/*
 * Created on 30 juin 2003
 *
 * Copyright (C) 2004, 2005, 2006 Aelitis SAS, All rights Reserved
 *
 * 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.
 *
 * 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 ( see the LICENSE file ).
 *
 * 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
 *
 * AELITIS, SAS au capital de 46,603.30 euros,
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */

package org.gudy.azureus2.ui.swt.views;

import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

import org.gudy.azureus2.core3.category.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerListener;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.*;
import org.gudy.azureus2.ui.swt.URLTransfer;
import org.gudy.azureus2.ui.swt.help.HealthHelpWindow;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.mainwindow.TorrentOpener;
import org.gudy.azureus2.ui.swt.minibar.DownloadBar;
import org.gudy.azureus2.ui.swt.shells.InputShell;
import org.gudy.azureus2.ui.swt.views.ViewUtils.SpeedAdapter;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWT;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWTMenuFillListener;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWTPanelCreator;
import org.gudy.azureus2.ui.swt.views.table.impl.TableCellImpl;
import org.gudy.azureus2.ui.swt.views.table.impl.TableViewSWTImpl;
import org.gudy.azureus2.ui.swt.views.table.impl.TableViewTab;
import org.gudy.azureus2.ui.swt.views.utils.ManagerUtils;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.plugins.net.buddy.BuddyPlugin;
import com.aelitis.azureus.plugins.net.buddy.BuddyPluginBuddy;
import com.aelitis.azureus.ui.UIFunctions;
import com.aelitis.azureus.ui.UIFunctionsManager;
import com.aelitis.azureus.ui.common.table.*;
import com.aelitis.azureus.ui.selectedcontent.ISelectedContent;
import com.aelitis.azureus.ui.selectedcontent.SelectedContent;
import com.aelitis.azureus.ui.selectedcontent.SelectedContentManager;
import com.aelitis.azureus.ui.swt.UIFunctionsSWT;
import com.aelitis.azureus.ui.swt.imageloader.ImageLoader;

import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.download.DownloadTypeComplete;
import org.gudy.azureus2.plugins.download.DownloadTypeIncomplete;
import org.gudy.azureus2.plugins.utils.StaticUtilities;

/** Displays a list of torrents in a table view.
 *
 * @author Olivier
 * @author TuxPaper
 *         2004/Apr/18: Use TableRowImpl instead of PeerRow
 *         2004/Apr/20: Remove need for tableItemToObject
 *         2004/Apr/21: extends TableView instead of IAbstractView
 *         2005/Oct/01: Column moving in SWT >= 3.1
 */
public class MyTorrentsView
       extends TableViewTab
       implements GlobalManagerListener,
                  ParameterListener,
                  DownloadManagerListener,
                  CategoryManagerListener,
                  CategoryListener,
                  KeyListener,
                  TableLifeCycleListener, 
                  TableViewSWTPanelCreator,
                  TableSelectionListener,
                  TableViewSWTMenuFillListener,
                  TableRefreshListener,
                  TableCountChangeListener
{
	private static final LogIDs LOGID = LogIDs.GUI;
	private static final int ASYOUTYPE_MODE_FIND = 0;
	private static final int ASYOUTYPE_MODE_FILTER = 1;
	private static final int ASYOUTYPE_MODE = ASYOUTYPE_MODE_FILTER;
	private static final int ASYOUTYPE_UPDATEDELAY = 300;
	
	/** Experimental Table UI.  When setting to true, some code needs 
	 *  uncommenting as well */
	private static final boolean EXPERIMENT = false;
	
	private AzureusCore		azureus_core;

  private GlobalManager globalManager;
  protected boolean isSeedingView;

  private Composite cTablePanel;
  private Font fontButton = null;
  private Composite cCategories;
  private ControlAdapter catResizeAdapter;
  private DragSource dragSource = null;
  private DropTarget dropTarget = null;
  private Composite cHeader = null;
  private Label lblHeader = null;
  private Text txtFilter = null;
  private Label lblX = null;
  
  private Category currentCategory;

  // table item index, where the drag has started
  private int drag_drop_line_start = -1;
  private TableRowCore[] drag_drop_rows = null;

	private TimerEvent searchUpdateEvent;
  private String sLastSearch = "";
  private long lLastSearchTime;
  private boolean bRegexSearch = false;
	private boolean bDNDalwaysIncomplete;
	private TableViewSWT tv;
	private Composite cTableParentPanel;
	protected boolean viewActive;
	private boolean forceHeaderVisible = false;
	private Label lblFilter;
	private TableSelectionListener defaultSelectedListener;

	public
	MyTorrentsView() {
	}
	
  /**
   * Initialize
   * 
   * @param _azureus_core
   * @param isSeedingView
   * @param basicItems
   */
  public 
  MyTorrentsView(
  		AzureusCore				_azureus_core,
  		String						tableID,
  		boolean 					isSeedingView,
      TableColumnCore[]	basicItems) 
  {
  	init(_azureus_core, tableID, isSeedingView, basicItems);
  }
  
  public void init(AzureusCore _azureus_core, String tableID,
			boolean isSeedingView, TableColumnCore[] basicItems) {

  	this.isSeedingView 	= isSeedingView;
  	
  	if (EXPERIMENT) {
//      tv = new ListView(isSeedingView ? TableManager.TABLE_MYTORRENTS_COMPLETE
//  				: TableManager.TABLE_MYTORRENTS_INCOMPLETE, SWT.V_SCROLL);
//      tv.setColumnList(basicItems, "#", false);
  	} else {
      tv = createTableView(tableID, basicItems);
  	}
    setTableView(tv);
    tv.setRowDefaultIconSize(new Point(16, 16));
    
    /*
     * 'Big' table has taller row height
     */
    if (getRowDefaultHeight() > 0) {
			tv.setRowDefaultHeight(getRowDefaultHeight());
		}
    
    azureus_core		= _azureus_core;
    this.globalManager 	= azureus_core.getGlobalManager();
    

    currentCategory = CategoryManager.getCategory(Category.TYPE_ALL);
    tv.addLifeCycleListener(this);
    tv.setMainPanelCreator(this);
    tv.addSelectionListener(this, false);
    tv.addMenuFillListener(this);
    tv.addRefreshListener(this, false);
    tv.addCountChangeListener(this);
    tv.setDataSourceType(isSeedingView ? DownloadTypeComplete.class
				: DownloadTypeIncomplete.class);

		//tv.setEnableTabViews(true);
		//IView views[] = { new GeneralView(), new PeersView(),
		//	new PeersGraphicView(), new PiecesView(), new FilesView(),
		//	new LoggerView() };
    //tv.setCoreTabViews(views);
	}

  // @see com.aelitis.azureus.ui.common.table.TableLifeCycleListener#tableViewInitialized()
  public void tableViewInitialized() {
    tv.addKeyListener(this);

    createTabs();

    createDragDrop();

    COConfigurationManager.addAndFireParameterListeners(new String[] {
			"DND Always In Incomplete",
			"Confirm Data Delete",
		}, this);

    if (currentCategory != null) {
    	currentCategory.addCategoryListener(this);
    }
    CategoryManager.addCategoryManagerListener(this);
    globalManager.addListener(this, false);
    Object[] dms = globalManager.getDownloadManagers().toArray();
    for (int i = 0; i < dms.length; i++) {
			DownloadManager dm = (DownloadManager) dms[i];
			dm.addListener(this);
			if (!isOurDownloadManager(dm)) {
				dms[i] = null;
			}
		}
    tv.addDataSources(dms);
    tv.processDataSourceQueue();
    
    cTablePanel.layout();
  }

  // @see com.aelitis.azureus.ui.common.table.TableLifeCycleListener#tableViewDestroyed()
  public void tableViewDestroyed() {
  	tv.removeKeyListener(this);
  	
  	Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				try {
					Utils.disposeSWTObjects(new Object[] {
						dragSource,
						dropTarget,
						fontButton
					});
					dragSource = null;
					dropTarget = null;
					fontButton = null;
				} catch (Exception e) {
					Debug.out(e);
				}
			}
		});
    Object[] dms = globalManager.getDownloadManagers().toArray();
    for (int i = 0; i < dms.length; i++) {
			DownloadManager dm = (DownloadManager) dms[i];
			dm.removeListener(this);
		}
    if (currentCategory != null) {
    	currentCategory.removeCategoryListener(this);
    }
    CategoryManager.removeCategoryManagerListener(this);
    globalManager.removeListener(this);
    COConfigurationManager.removeParameterListener("DND Always In Incomplete", this);
    COConfigurationManager.removeParameterListener("Confirm Data Delete", this);
    COConfigurationManager.removeParameterListener("User Mode", this);
  }
  
  
  // @see org.gudy.azureus2.ui.swt.views.table.TableViewSWTPanelCreator#createTableViewPanel(org.eclipse.swt.widgets.Composite)
  public Composite createTableViewPanel(Composite composite) {
  	composite.addListener(SWT.Activate, new Listener() {
			public void handleEvent(Event event) {
				viewActive = true;
		    updateSelectedContent();
		    refreshIconBar();
			}
		});
  	composite.addListener(SWT.Deactivate, new Listener() {
			public void handleEvent(Event event) {
				viewActive = false;
				// don't updateSelectedContent() because we may have switched
				// to a button or a text field, and we still want out content to be
				// selected
			}
		});
  	
    GridData gridData;
    cTableParentPanel = new Composite(composite, SWT.NULL);
    GridLayout layout = new GridLayout();
    layout.numColumns = 2;
    layout.horizontalSpacing = 0;
    layout.verticalSpacing = 0;
    layout.marginHeight = 0;
    layout.marginWidth = 0;
    cTableParentPanel.setLayout(layout);
    if (composite.getLayout() instanceof GridLayout) {
    	cTableParentPanel.setLayoutData(new GridData(GridData.FILL_BOTH));
    }
    
    if (EXPERIMENT) {
    	Composite cHeaders = new Composite(cTableParentPanel, SWT.NONE);
    	gridData = new GridData(GridData.FILL_HORIZONTAL);
    	gridData.horizontalSpan = 2;
    	GC gc = new GC(cHeaders);
    	int h = gc.textExtent("alyup").y + 2;
    	gc.dispose();
    	gridData.heightHint = h;
    	cHeaders.setLayoutData(gridData);
    	//((ListView)tv).setHeaderArea(cHeaders, null, null);
    }

    cTablePanel = new Composite(cTableParentPanel, SWT.NULL);
    cTablePanel.setBackground(composite.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
    cTablePanel.setForeground(composite.getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND));

    gridData = new GridData(GridData.FILL_BOTH);
    gridData.horizontalSpan = 2;
    cTablePanel.setLayoutData(gridData);

    layout = new GridLayout(1, false);
    layout.marginHeight = 0;
    layout.marginWidth = 0;
    layout.verticalSpacing = 0;
    layout.horizontalSpacing = 0;
    cTablePanel.setLayout(layout);

    cTablePanel.layout();
    return cTablePanel;
  }
  
  public void setForceHeaderVisible(boolean forceHeaderVisible) {
		this.forceHeaderVisible  = forceHeaderVisible;
		if (cTablePanel != null && !cTablePanel.isDisposed()) {
			createTabs();
		}
  }

  private void createTabs() {
    GridData gridData;
    Category[] categories = CategoryManager.getCategories();
    Arrays.sort(categories);
    boolean showCat = sLastSearch.length() > 0;
    if (!showCat) {
	    for(int i = 0; i < categories.length; i++) {
	        if(categories[i].getType() == Category.TYPE_USER) {
	            showCat = true;
	            break;
	        }
	    }
    }
    boolean show = showCat || forceHeaderVisible;

    if (!showCat) {
    	if (cCategories != null && !cCategories.isDisposed()) {
        Control[] controls = cCategories.getChildren();
        for (int i = 0; i < controls.length; i++) {
          controls[i].dispose();
        }
    	}
    }
    
    if (show) {
      if (cCategories == null) {
        Composite parent = cTableParentPanel;

        cCategories = new Composite(parent, SWT.NONE);
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
        cCategories.setLayoutData(gridData);
        RowLayout rowLayout = new RowLayout();
        rowLayout.marginTop = 0;
        rowLayout.marginBottom = 0;
        rowLayout.marginLeft = 3;
        rowLayout.marginRight = 0;
        rowLayout.spacing = 0;
        rowLayout.wrap = true;
        new Label(cCategories, SWT.NONE);
        cCategories.setLayout(rowLayout);

        cHeader = new Composite(parent, SWT.NONE);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalIndent = 5;
        cHeader.setLayoutData(gridData);
        GridLayout layout = new GridLayout();
        layout.numColumns = 6;
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.horizontalSpacing = 2;
        layout.verticalSpacing = 0;
        cHeader.setLayout(layout);
        
        lblHeader = new Label(cHeader, SWT.WRAP);
        gridData = new GridData();
        lblHeader.setLayoutData(gridData);
        updateTableLabel();

        Label lblSep = new Label(cHeader, SWT.SEPARATOR | SWT.VERTICAL);
        gridData = new GridData(GridData.FILL_VERTICAL);
        gridData.heightHint = 5;
        lblSep.setLayoutData(gridData);
        
        lblFilter = new Label(cHeader, SWT.WRAP);
        gridData = new GridData(GridData.BEGINNING);
        lblFilter.setLayoutData(gridData);
        Messages.setLanguageText(lblFilter, "MyTorrentsView.filter");

		lblX = new Label(cHeader, SWT.WRAP);
        Messages.setLanguageTooltip(lblX, "MyTorrentsView.clearFilter.tooltip");
        gridData = new GridData(SWT.TOP);
        lblX.setLayoutData(gridData);
        ImageLoader.getInstance().setLabelImage(lblX, "smallx-gray");
        lblX.setData("ImageID", "smallx-gray");
        lblX.addMouseListener(new MouseAdapter() {
        	public void mouseUp(MouseEvent e) {
        		if (e.y <= 10) {
          		sLastSearch = "";
          		updateLastSearch();
        		}
        	}
        });
        
        txtFilter = new Text(cHeader, SWT.BORDER);
        Messages.setLanguageTooltip(txtFilter, "MyTorrentsView.filter.tooltip");
        txtFilter.addKeyListener(this);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        txtFilter.setLayoutData(gridData);
        txtFilter.addModifyListener(new ModifyListener() {
        	public void modifyText(ModifyEvent e) {
        		sLastSearch = ((Text)e.widget).getText();
        		updateLastSearch();
        	}
        });
        txtFilter.addKeyListener(new KeyAdapter() {
        	public void keyPressed(KeyEvent e) {
        		if (e.keyCode == SWT.ARROW_DOWN) {
        			tv.setFocus();
        			e.doit = false;
        		} else if (e.character == 13) {
        			if (searchUpdateEvent != null) {
        				searchUpdateEvent.cancel();
        			}
        			searchUpdateEvent = null;
        			activateCategory(currentCategory);
        		}
        	}
        });
        
        lblSep = new Label(cHeader, SWT.SEPARATOR | SWT.VERTICAL);
        gridData = new GridData(GridData.FILL_VERTICAL);
        gridData.heightHint = 5;
        lblSep.setLayoutData(gridData);
        
        cHeader.moveAbove(null);
        cCategories.moveBelow(cHeader);
      } else {
        Control[] controls = cCategories.getChildren();
        for (int i = 0; i < controls.length; i++) {
          controls[i].dispose();
        }
      }
      
      txtFilter.setVisible(showCat);
      lblX.setVisible(showCat);
      lblFilter.setVisible(showCat);

      if (showCat) {
      	buildCat(categories);
      }
    }
  }
  
  /**
	 * 
	 *
	 * @param categories 
   * @since 3.1.1.1
	 */
	private void buildCat(Category[] categories) {
		int iFontPixelsHeight = 11;
		int iFontPointHeight = (iFontPixelsHeight * 72)
				/ cCategories.getDisplay().getDPI().y;
		for (int i = 0; i < categories.length; i++) {
			final Category category = categories[i];

			final Button catButton = new Button(cCategories, SWT.TOGGLE);
			catButton.addKeyListener(this);
			if (i == 0 && fontButton == null) {
				Font f = catButton.getFont();
				FontData fd = f.getFontData()[0];
				fd.setHeight(iFontPointHeight);
				fontButton = new Font(cCategories.getDisplay(), fd);
			}
			catButton.setText("|");
			catButton.setFont(fontButton);
			catButton.pack(true);
			if (catButton.computeSize(100, SWT.DEFAULT).y > 0) {
				RowData rd = new RowData();
				rd.height = catButton.computeSize(100, SWT.DEFAULT).y - 2
						+ catButton.getBorderWidth() * 2;
				catButton.setLayoutData(rd);
			}

			final String name = category.getName();
			if (category.getType() == Category.TYPE_USER)
				catButton.setText(name);
			else
				Messages.setLanguageText(catButton, name);

			catButton.setData("Category", category);
			if (category == currentCategory) {
				catButton.setSelection(true);
			}

			catButton.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					Button curButton = (Button) e.widget;
					boolean isEnabled = curButton.getSelection();
					Control[] controls = cCategories.getChildren();
					if (!isEnabled) {
						for (int i = 0; i < controls.length; i++) {
							if (controls[i] instanceof Button) {
								curButton = (Button) controls[i];
								break;
							}
						}
					}

					for (int i = 0; i < controls.length; i++) {
						if (!(controls[i] instanceof Button)) {
							continue;
						}
						Button b = (Button) controls[i];
						if (b != curButton && b.getSelection())
							b.setSelection(false);
						else if (b == curButton && !b.getSelection())
							b.setSelection(true);
					}
					activateCategory((Category) curButton.getData("Category"));
				}
			});

			catButton.addListener(SWT.MouseHover, new Listener() {
				public void handleEvent(Event event) {
					Button curButton = (Button) event.widget;
					Category curCategory = (Category) curButton.getData("Category");
					List dms = curCategory.getDownloadManagers(globalManager.getDownloadManagers());

					long ttlActive = 0;
					long ttlSize = 0;
					long ttlRSpeed = 0;
					long ttlSSpeed = 0;
					int count = 0;
					for (Iterator iter = dms.iterator(); iter.hasNext();) {
						DownloadManager dm = (DownloadManager) iter.next();

						if (!isInCategory(dm, currentCategory))
							continue;

						count++;
						if (dm.getState() == DownloadManager.STATE_DOWNLOADING
								|| dm.getState() == DownloadManager.STATE_SEEDING)
							ttlActive++;
						ttlSize += dm.getSize();
						ttlRSpeed += dm.getStats().getDataReceiveRate();
						ttlSSpeed += dm.getStats().getDataSendRate();
					}

					String up_details = "";
					String down_details = "";

					if (category.getType() != Category.TYPE_ALL) {

						String up_str = MessageText.getString("GeneralView.label.maxuploadspeed");
						String down_str = MessageText.getString("GeneralView.label.maxdownloadspeed");
						String unlimited_str = MessageText.getString("MyTorrentsView.menu.setSpeed.unlimited");

						int up_speed = category.getUploadSpeed();
						int down_speed = category.getDownloadSpeed();

						up_details = up_str
								+ ": "
								+ (up_speed == 0 ? unlimited_str
										: DisplayFormatters.formatByteCountToKiBEtc(up_speed));
						down_details = down_str
								+ ": "
								+ (down_speed == 0 ? unlimited_str
										: DisplayFormatters.formatByteCountToKiBEtc(down_speed));
					}

					if (count == 0) {
						curButton.setToolTipText(down_details + "\n" + up_details
								+ "\nTotal: 0");
						return;
					}

					curButton.setToolTipText((up_details.length() == 0 ? ""
							: (down_details + "\n" + up_details + "\n"))
							+ "Total: "
							+ count
							+ "\n"
							+ "Downloading/Seeding: "
							+ ttlActive
							+ "\n"
							+ "\n"
							+ "Speed: "
							+ DisplayFormatters.formatByteCountToKiBEtcPerSec(ttlRSpeed
									/ count)
							+ "/"
							+ DisplayFormatters.formatByteCountToKiBEtcPerSec(ttlSSpeed
									/ count)
							+ "\n"
							+ "Size: "
							+ DisplayFormatters.formatByteCountToKiBEtc(ttlSize));
				}
			});

			final DropTarget tabDropTarget = new DropTarget(catButton,
					DND.DROP_DEFAULT | DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
			Transfer[] types = new Transfer[] {
				TextTransfer.getInstance()
			};
			tabDropTarget.setTransfer(types);
			tabDropTarget.addDropListener(new DropTargetAdapter() {
				public void dragOver(DropTargetEvent e) {
					if (drag_drop_line_start >= 0)
						e.detail = DND.DROP_MOVE;
					else
						e.detail = DND.DROP_NONE;
				}

				public void drop(DropTargetEvent e) {
					e.detail = DND.DROP_NONE;
					//System.out.println("DragDrop on Button:" + drag_drop_line_start);
					if (drag_drop_line_start >= 0) {
						drag_drop_line_start = -1;
						drag_drop_rows = null;

						TorrentUtil.assignToCategory(tv.getSelectedDataSources(),
								(Category) catButton.getData("Category"));
					}
				}
			});

			catButton.addDisposeListener(new DisposeListener() {
				public void widgetDisposed(DisposeEvent e) {
					if (tabDropTarget != null && !tabDropTarget.isDisposed()) {
						tabDropTarget.dispose();
					}
				}
			});

			final Menu menu = new Menu(getComposite().getShell(), SWT.POP_UP);

			catButton.setMenu(menu);

			menu.addMenuListener(new MenuListener() {
				boolean bShown = false;

				public void menuHidden(MenuEvent e) {
					bShown = false;

					if (Constants.isOSX)
						return;

					// Must dispose in an asyncExec, otherwise SWT.Selection doesn't
					// get fired (async workaround provided by Eclipse Bug #87678)

					e.widget.getDisplay().asyncExec(new AERunnable() {
						public void runSupport() {
							if (bShown || menu.isDisposed())
								return;
							MenuItem[] items = menu.getItems();
							for (int i = 0; i < items.length; i++) {
								items[i].dispose();
							}
						}
					});
				}

				public void menuShown(MenuEvent e) {
					MenuItem[] items = menu.getItems();
					for (int i = 0; i < items.length; i++)
						items[i].dispose();

					bShown = true;

					if (category.getType() == Category.TYPE_USER) {

						final MenuItem itemDelete = new MenuItem(menu, SWT.PUSH);

						Messages.setLanguageText(itemDelete,
								"MyTorrentsView.menu.category.delete");

						menu.setDefaultItem(itemDelete);

						itemDelete.addListener(SWT.Selection, new Listener() {
							public void handleEvent(Event event) {
								Category catToDelete = (Category) catButton.getData("Category");
								if (catToDelete != null) {
									java.util.List managers = catToDelete.getDownloadManagers(globalManager.getDownloadManagers());
									// move to array,since setcategory removed it from the category,
									// which would mess up our loop
									DownloadManager dms[] = (DownloadManager[]) managers.toArray(new DownloadManager[managers.size()]);
									for (int i = 0; i < dms.length; i++) {
										dms[i].getDownloadState().setCategory(null);
									}
									if (currentCategory == catToDelete) {

										activateCategory(CategoryManager.getCategory(Category.TYPE_ALL));

									} else {
										// always activate as deletion of this one might have
										// affected the current view 
										activateCategory(currentCategory);
									}
									CategoryManager.removeCategory(catToDelete);
								}
							}
						});
					}

					if (category.getType() != Category.TYPE_ALL) {

						long maxDownload = COConfigurationManager.getIntParameter(
								"Max Download Speed KBs", 0) * 1024;
						long maxUpload = COConfigurationManager.getIntParameter(
								"Max Upload Speed KBs", 0) * 1024;

						int down_speed = category.getDownloadSpeed();
						int up_speed = category.getUploadSpeed();

						ViewUtils.addSpeedMenu(menu.getShell(), menu, true, true, false,
								down_speed == 0, down_speed, down_speed, maxDownload, false,
								up_speed == 0, up_speed, up_speed, maxUpload, 1,
								new SpeedAdapter() {
									public void setDownSpeed(int val) {
										category.setDownloadSpeed(val);
									}

									public void setUpSpeed(int val) {
										category.setUploadSpeed(val);

									}
								});
					}

					java.util.List managers = category.getDownloadManagers(globalManager.getDownloadManagers());

					final DownloadManager dms[] = (DownloadManager[]) managers.toArray(new DownloadManager[managers.size()]);

					boolean start = false;
					boolean stop = false;

					for (int i = 0; i < dms.length; i++) {

						DownloadManager dm = dms[i];

						stop = stop || ManagerUtils.isStopable(dm);

						start = start || ManagerUtils.isStartable(dm);

					}

					// Queue

					final MenuItem itemQueue = new MenuItem(menu, SWT.PUSH);
					Messages.setLanguageText(itemQueue, "MyTorrentsView.menu.queue");
					Utils.setMenuItemImage(itemQueue, "start");
					itemQueue.addListener(SWT.Selection, new Listener() {
						public void handleEvent(Event event) {
							TorrentUtil.queueTorrents(dms, menu.getShell());
						}
					});
					itemQueue.setEnabled(start);

					// Stop

					final MenuItem itemStop = new MenuItem(menu, SWT.PUSH);
					Messages.setLanguageText(itemStop, "MyTorrentsView.menu.stop");
					Utils.setMenuItemImage(itemStop, "stop");
					itemStop.addListener(SWT.Selection, new Listener() {
						public void handleEvent(Event event) {
							TorrentUtil.stopTorrents(dms, menu.getShell());
						}
					});
					itemStop.setEnabled(stop);

					// share with friends
					
					PluginInterface bpi = StaticUtilities.getDefaultPluginInterface().getPluginManager().getPluginInterfaceByClass( BuddyPlugin.class );
					
					int cat_type = category.getType();
					
					if ( bpi != null && cat_type != Category.TYPE_UNCATEGORIZED ){
						
						BuddyPlugin	buddy_plugin = (BuddyPlugin)bpi.getPlugin();
						
						if ( buddy_plugin.isEnabled()){
							
							final Menu share_menu = new Menu(menu.getShell(), SWT.DROP_DOWN);
							final MenuItem share_item = new MenuItem(menu, SWT.CASCADE);
							Messages.setLanguageText(share_item, "azbuddy.ui.menu.cat.share" );
							share_item.setMenu(share_menu);

						
							List<BuddyPluginBuddy> buddies = buddy_plugin.getBuddies();
							
							for ( final BuddyPluginBuddy buddy: buddies ){
								
								if ( buddy.getNickName() == null ){
									
									continue;
								}
								
								final String cname;
								
								if ( cat_type == Category.TYPE_ALL ){
									
									cname = "All";
									
								}else{
									
									cname = category.getName();
								}
								
								final boolean auth = buddy.isLocalRSSCategoryAuthorised( cname );
								
								final MenuItem itemShare = new MenuItem(share_menu, SWT.CHECK );
								
								itemShare.setText( buddy.getName());
								
								itemShare.setSelection( auth );
								
								itemShare.addListener(
									SWT.Selection, 
									new Listener() 
									{
										public void 
										handleEvent(
											Event event) 
										{
											if ( auth ){
											
												buddy.removeLocalAuthorisedRSSCategory( cname );
												
											}else{
												
												buddy.addLocalAuthorisedRSSCategory( cname );
											}
										}
									});
								
							}
						}
					}
					
					// options

					MenuItem itemOptions = new MenuItem(menu, SWT.PUSH);

					Messages.setLanguageText(itemOptions,
							"MainWindow.menu.view.configuration");
					itemOptions.addListener(SWT.Selection, new Listener() {
						public void handleEvent(Event event) {
							UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions();

							uiFunctions.openView(UIFunctions.VIEW_DM_MULTI_OPTIONS, dms);
						}
					});

					if (dms.length == 0) {

						itemOptions.setEnabled(false);
					}
				}
			});
		}

		cCategories.layout();
		getComposite().layout();

		// layout hack - relayout
		if (catResizeAdapter == null) {
			catResizeAdapter = new ControlAdapter() {
				public void controlResized(ControlEvent event) {
					if (getComposite().isDisposed() || cCategories.isDisposed())
						return;

					GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);

					int parentWidth = cCategories.getParent().getClientArea().width;
					int catsWidth = cCategories.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
					// give text a 5 pixel right padding
					int textWidth = 5 + cHeader.computeSize(SWT.DEFAULT, SWT.DEFAULT).x
							+ cHeader.getBorderWidth() * 2;

					Object layoutData = cHeader.getLayoutData();
					if (layoutData instanceof GridData) {
						GridData labelGrid = (GridData) layoutData;
						textWidth += labelGrid.horizontalIndent;
					}

					if (textWidth + catsWidth > parentWidth) {
						gridData.widthHint = parentWidth - textWidth;
					}
					cCategories.setLayoutData(gridData);
					cCategories.getParent().layout(true);

				}
			};

			tv.getTableComposite().addControlListener(catResizeAdapter);
		}

		catResizeAdapter.controlResized(null);
	}

	public boolean isOurDownloadManager(DownloadManager dm) {
  	if (!isInCategory(dm, currentCategory)) {
  		return false;
  	}

		boolean bCompleted =  dm.isDownloadComplete(bDNDalwaysIncomplete);
		boolean bOurs = (bCompleted && isSeedingView)
				|| (!bCompleted && !isSeedingView);
		
//		System.out.println("ourDM? " + sTableID + "; " + dm.getDisplayName()
//				+ "; Complete=" + bCompleted + ";Ours=" + bOurs + ";bc"
//				+ dm.getStats().getDownloadCompleted(false) + ";"
//				+ dm.getStats().getDownloadCompleted(true));

		if (bOurs) {
			bOurs = filterCheck(dm);
		}

		return bOurs;
	}
	
	public boolean filterCheck(DownloadManager dm) {
		boolean bOurs = true;
		if (sLastSearch.length() > 0) {
			try {
				String[][] names = {
					{
						"",
						dm.getDisplayName()
					},
					{
						"t:",
						dm.getTorrent().getAnnounceURL().getHost()
					},
					{
						"st:",
						"" + dm.getState()
					}
				};

				String name = names[0][1];
				String tmpSearch = sLastSearch;

				for (int i = 0; i < names.length; i++) {
					if (tmpSearch.startsWith(names[i][0])) {
						tmpSearch = tmpSearch.substring(names[i][0].length());
						name = names[i][1];
					}
				}

				String s = bRegexSearch ? tmpSearch : "\\Q"
						+ tmpSearch.replaceAll("[|;]", "\\\\E|\\\\Q") + "\\E";
				Pattern pattern = Pattern.compile(s, Pattern.CASE_INSENSITIVE);

				if (!pattern.matcher(name).find())
					bOurs = false;
			} catch (Exception e) {
				// Future: report PatternSyntaxException message to user.
			}
		}
		return bOurs;
	}

  // @see com.aelitis.azureus.ui.common.table.TableSelectionListener#selected(com.aelitis.azureus.ui.common.table.TableRowCore[])
  public void selected(TableRowCore[] rows) {
  	updateSelectedContent();
  	refreshTorrentMenu();
  	refreshIconBar();
  }

	// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#deselected(com.aelitis.azureus.ui.common.table.TableRowCore[])
	public void deselected(TableRowCore[] rows) {
  	updateSelectedContent();
  	refreshTorrentMenu();
  	refreshIconBar();
	}

	// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#focusChanged(com.aelitis.azureus.ui.common.table.TableRowCore)
	public void focusChanged(TableRowCore focus) {
  	refreshIconBar();
  	refreshTorrentMenu();
	}

	// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#mouseEnter(com.aelitis.azureus.ui.common.table.TableRowCore)
	public void mouseEnter(TableRowCore row) {
	}

	// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#mouseExit(com.aelitis.azureus.ui.common.table.TableRowCore)
	public void mouseExit(TableRowCore row) {
	}

	public void updateSelectedContent() {
		if (cTablePanel == null || cTablePanel.isDisposed()
				|| !cTablePanel.isVisible()) {
			return;
		}
		DownloadManager[] dms = getSelectedDownloads();
		ISelectedContent[] sc = new ISelectedContent[dms.length];
		int pos = 0;
		for (int i = 0; i < dms.length; i++) {
			DownloadManager dm = dms[i];
			if (dm != null) {
				try {
					sc[pos] = new SelectedContent(dm);
					pos++;
				} catch (Exception e) {
				}
			}
		}
		if (pos != dms.length) {
			ISelectedContent[] sc_temp = new ISelectedContent[pos];
			
			System.arraycopy(sc, 0, sc_temp, 0, pos);
			
			sc = sc_temp;
		}
		SelectedContentManager.changeCurrentlySelectedContent(tv.getTableID(), sc, tv);
	}

  private void refreshIconBar() {
  	computePossibleActions();
  	UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions();
  	if (uiFunctions != null) {
  		uiFunctions.refreshIconBar();
  	}
  }
  
	private void refreshTorrentMenu() {
		UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions();
		if (uiFunctions != null && uiFunctions instanceof UIFunctionsSWT) {
			((UIFunctionsSWT)uiFunctions).refreshTorrentMenu();
		}
	}
	
	public DownloadManager[] getSelectedDownloads() {
		Object[] data_sources = tv.getSelectedDataSources();
		DownloadManager[] result = new DownloadManager[data_sources.length];
		System.arraycopy(data_sources, 0, result, 0, result.length);
		return result;
	}

  // @see com.aelitis.azureus.ui.common.table.TableSelectionListener#defaultSelected(com.aelitis.azureus.ui.common.table.TableRowCore[])
  public void defaultSelected(TableRowCore[] rows, int keyMask) {
  	if (defaultSelectedListener != null) {
  		defaultSelectedListener.defaultSelected(rows, keyMask);
  		return;
  	}
  	showSelectedDetails();
	}
  
  private void showSelectedDetails() {
		Object[] dm_sources = tv.getSelectedDataSources();
		UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions();
		for (int i = 0; i < dm_sources.length; i++) {
			if (dm_sources[i] == null) {
				continue;
			}
			if (uiFunctions != null) {
				uiFunctions.openView(UIFunctions.VIEW_DM_DETAILS,
						(DownloadManager) dm_sources[i]);
			}
		}  	
  }
  
  public void overrideDefaultSelected(TableSelectionListener defaultSelectedListener) {
		this.defaultSelectedListener = defaultSelectedListener;
  }



  /* SubMenu for column specific tasks.
   */
  // @see org.gudy.azureus2.ui.swt.views.table.TableViewSWTMenuFillListener#addThisColumnSubMenu(java.lang.String, org.eclipse.swt.widgets.Menu)
  public void addThisColumnSubMenu(String sColumnName, Menu menuThisColumn) {
    if (sColumnName.equals("health")) {
      MenuItem item = new MenuItem(menuThisColumn, SWT.PUSH);
      Messages.setLanguageText(item, "MyTorrentsView.menu.health");
      Utils.setMenuItemImage(item, "st_explain");
      item.addListener(SWT.Selection, new Listener() {
        public void handleEvent(Event e) {
          HealthHelpWindow.show(Display.getDefault());
        }
      });

    } else if (sColumnName.equals("maxuploads")) {
      int iStart = COConfigurationManager.getIntParameter("Max Uploads") - 2;
      if (iStart < 2) iStart = 2;
      for (int i = iStart; i < iStart + 6; i++) {
        MenuItem item = new MenuItem(menuThisColumn, SWT.PUSH);
        item.setText(String.valueOf(i));
        item.setData("MaxUploads", new Long(i));
        item.addListener(SWT.Selection,
                         new TableSelectedRowsListener(tv) {
          public void run(TableRowCore row) {
            DownloadManager dm = (DownloadManager)row.getDataSource(true);
            MenuItem item = (MenuItem)event.widget;
            if (item != null) {
              int value = ((Long)item.getData("MaxUploads")).intValue();
              dm.setMaxUploads(value);
            }
          } // run
        }); // listener
      } // for
    }
  }
  
	  public void fillMenu(final Menu menu) {
			Object[] dm_items = tv.getSelectedDataSources();
			boolean hasSelection = (dm_items.length > 0);

			if (hasSelection) {
				DownloadManager[] dms = new DownloadManager[dm_items.length];
				for (int i=0; i<dm_items.length; i++) {
					dms[i] = (DownloadManager)dm_items[i];
				}
				TorrentUtil.fillTorrentMenu(menu, dms, azureus_core, cTablePanel, true,
					(isSeedingView) ? 2 : 1, tv);

				// ---
				new MenuItem(menu, SWT.SEPARATOR);
			}
			
			final MenuItem itemFilter = new MenuItem(menu, SWT.PUSH);
			Messages.setLanguageText(itemFilter, "MyTorrentsView.menu.filter");
			itemFilter.addListener(SWT.Selection, new Listener() {
				public void handleEvent(Event event) {
					openFilterDialog();
				}
			});

  }

	private void createDragDrop() {
		try {

			Transfer[] types = new Transfer[] { TextTransfer.getInstance() };

			if (dragSource != null && !dragSource.isDisposed()) {
				dragSource.dispose();
			}

			if (dropTarget != null && !dropTarget.isDisposed()) {
				dropTarget.dispose();
			}

			dragSource = tv.createDragSource(DND.DROP_MOVE);
			if (dragSource != null) {
				dragSource.setTransfer(types);
				dragSource.addDragListener(new DragSourceAdapter() {
					public void dragStart(DragSourceEvent event) {
						TableRowCore[] rows = tv.getSelectedRows();
						if (rows.length != 0) {
							event.doit = true;
							// System.out.println("DragStart");
							drag_drop_line_start = rows[0].getIndex();
							drag_drop_rows = rows;
						} else {
							event.doit = false;
							drag_drop_line_start = -1;
							drag_drop_rows = null;
						}
					}

					public void dragSetData(DragSourceEvent event) {
						// System.out.println("DragSetData");
						event.data = "moveRow";
					}
				});
			}

			dropTarget = tv.createDropTarget(DND.DROP_DEFAULT | DND.DROP_MOVE
					| DND.DROP_COPY | DND.DROP_LINK | DND.DROP_TARGET_MOVE);
			if (dropTarget != null) {
				if (SWT.getVersion() >= 3107) {
					dropTarget.setTransfer(new Transfer[] { HTMLTransfer.getInstance(),
							URLTransfer.getInstance(), FileTransfer.getInstance(),
							TextTransfer.getInstance() });
				} else {
					dropTarget.setTransfer(new Transfer[] { URLTransfer.getInstance(),
							FileTransfer.getInstance(), TextTransfer.getInstance() });
				}

				dropTarget.addDropListener(new DropTargetAdapter() {
					public void dropAccept(DropTargetEvent event) {
						event.currentDataType = URLTransfer.pickBestType(event.dataTypes,
								event.currentDataType);
					}

					public void dragEnter(DropTargetEvent event) {
						// no event.data on dragOver, use drag_drop_line_start to determine
						// if ours
						if (drag_drop_line_start < 0) {
							if (event.detail != DND.DROP_COPY) {
								if ((event.operations & DND.DROP_LINK) > 0)
									event.detail = DND.DROP_LINK;
								else if ((event.operations & DND.DROP_COPY) > 0)
									event.detail = DND.DROP_COPY;
							}
						} else if (TextTransfer.getInstance().isSupportedType(
								event.currentDataType)) {
							event.detail = event.item == null ? DND.DROP_NONE : DND.DROP_MOVE;
							event.feedback = DND.FEEDBACK_SCROLL | DND.FEEDBACK_INSERT_BEFORE;
						}
					}

					public void dragOver(DropTargetEvent event) {
						if (drag_drop_line_start >= 0) {
							event.detail = event.item == null ? DND.DROP_NONE : DND.DROP_MOVE;
							event.feedback = DND.FEEDBACK_SCROLL | DND.FEEDBACK_INSERT_BEFORE;
						}
					}

					public void drop(DropTargetEvent event) {
						if (!(event.data instanceof String)
								|| !((String) event.data).equals("moveRow")) {
							TorrentOpener.openDroppedTorrents(azureus_core, event, true);
							return;
						}

						event.detail = DND.DROP_NONE;
						// Torrent file from shell dropped
						if (drag_drop_line_start >= 0) { // event.data == null
							event.detail = DND.DROP_NONE;
							TableRowCore row = tv.getRow(event);
							if (row == null)
								return;
							int drag_drop_line_end = row.getIndex();
							if (drag_drop_line_end != drag_drop_line_start) {
								DownloadManager dm = (DownloadManager) row.getDataSource(true);
								moveRowsTo(drag_drop_rows, dm.getPosition());
								event.detail = DND.DROP_MOVE;
							}
							drag_drop_line_start = -1;
							drag_drop_rows = null;
						}
					}
				});
			}

		} catch (Throwable t) {
			Logger.log(new LogEvent(LOGID, "failed to init drag-n-drop", t));
		}
	}
  
  private void moveRowsTo(TableRowCore[] rows, int iNewPos) {
    if (rows == null || rows.length == 0) {
      return;
    }
    
    TableColumnCore sortColumn = tv.getSortColumn();
    boolean isSortAscending = sortColumn == null ? true
				: sortColumn.isSortAscending();

    for (int i = 0; i < rows.length; i++) {
			TableRowCore row = rows[i];
      DownloadManager dm = (DownloadManager)row.getDataSource(true);
      int iOldPos = dm.getPosition();
      
      globalManager.moveTo(dm, iNewPos);
      if (isSortAscending) {
        if (iOldPos > iNewPos)
          iNewPos++;
      } else {
        if (iOldPos < iNewPos)
          iNewPos--;
      }
    }

    boolean bForceSort = sortColumn.getName().equals("#");
    tv.columnInvalidate("#");
    tv.refreshTable(bForceSort);
  }

  // @see com.aelitis.azureus.ui.common.table.TableRefreshListener#tableRefresh()
  public void tableRefresh() {
    if (tv.isDisposed())
      return;
    
    refreshTorrentMenu();
  }


	// @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
	public void keyPressed(KeyEvent e) {
		int key = e.character;
		if (key <= 26 && key > 0)
			key += 'a' - 1;

		if (e.stateMask == (SWT.CTRL | SWT.SHIFT)) {
			// CTRL+SHIFT+S stop all Torrents
			if (key == 's') {
				ManagerUtils.asyncStopAll();
				e.doit = false;
				return;
			}

			// Can't capture Ctrl-PGUP/DOWN for moving up/down in chunks
			// (because those keys move through tabs), so use shift-ctrl-up/down
			if (e.keyCode == SWT.ARROW_DOWN) {
				moveSelectedTorrents(10);
				e.doit = false;
				return;
			}

			if (e.keyCode == SWT.ARROW_UP) {
				moveSelectedTorrents(-10);
				e.doit = false;
				return;
			}
		}
		
		if (e.stateMask == SWT.MOD1) {
			switch (key) {
				case 'a': // CTRL+A select all Torrents
					if (e.widget != txtFilter) {
						tv.selectAll();
						e.doit = false;
					}
					break;
				case 'c': // CTRL+C
					if (e.widget != txtFilter) {
						tv.clipboardSelected();
						e.doit = false;
					}
					break;
				case 'f': // CTRL+F Find/Filter
					openFilterDialog();
					e.doit = false;
					break;
				case 'i': // CTRL+I Info/Details
					showSelectedDetails();
					e.doit = false;
					break;
			}

			if (!e.doit)
				return;
		}

		if (e.stateMask == SWT.CTRL) {
			switch (e.keyCode) {
				case SWT.ARROW_UP:
					moveSelectedTorrentsUp();
					e.doit = false;
					break;
				case SWT.ARROW_DOWN:
					moveSelectedTorrentsDown();
					e.doit = false;
					break;
				case SWT.HOME:
					moveSelectedTorrentsTop();
					e.doit = false;
					break;
				case SWT.END:
					moveSelectedTorrentsEnd();
					e.doit = false;
					break;
			}
			if (!e.doit)
				return;

			switch (key) {
				case 'r': // CTRL+R resume/start selected Torrents
					TorrentUtil.resumeTorrents(tv.getSelectedDataSources());
					e.doit = false;
					break;
				case 's': // CTRL+S stop selected Torrents
					TorrentUtil.stopTorrents(tv.getSelectedDataSources(), cTablePanel.getShell());
					e.doit = false;
					break;
				case 'x': // CTRL+X: RegEx search switch
					bRegexSearch = !bRegexSearch;
					e.doit = false;
					updateLastSearch();
					break;
			}

			if (!e.doit)
				return;
		}

		// DEL remove selected Torrents
		if (e.stateMask == 0 && e.keyCode == SWT.DEL && e.widget != txtFilter) {
			TorrentUtil.removeTorrents(tv.getSelectedDataSources(), cTablePanel.getShell());
			e.doit = false;
			return;
		}

		if (e.keyCode != SWT.BS) {
			if ((e.stateMask & (~SWT.SHIFT)) != 0 || e.character < 32)
				return;
		}
		
		if (e.widget == txtFilter)
			return;

		// normal character: jump to next item with a name beginning with this character
		if (ASYOUTYPE_MODE == ASYOUTYPE_MODE_FIND) {
			if (System.currentTimeMillis() - lLastSearchTime > 3000)
				sLastSearch = "";
		}

		if (e.keyCode == SWT.BS) {
			if (e.stateMask == SWT.CONTROL)
				sLastSearch = "";
			else if (sLastSearch.length() > 0)
				sLastSearch = sLastSearch.substring(0, sLastSearch.length() - 1);
		} else
			sLastSearch += String.valueOf(e.character);

		if (ASYOUTYPE_MODE == ASYOUTYPE_MODE_FILTER) {
			if (txtFilter != null && !txtFilter.isDisposed()) {
				txtFilter.setFocus();
			}
			updateLastSearch();
		} else {
			TableCellCore[] cells = tv.getColumnCells("name");

			//System.out.println(sLastSearch);

			Arrays.sort(cells, TableCellImpl.TEXT_COMPARATOR);
			int index = Arrays.binarySearch(cells, sLastSearch,
					TableCellImpl.TEXT_COMPARATOR);
			if (index < 0) {

				int iEarliest = -1;
				String s = bRegexSearch ? sLastSearch : "\\Q" + sLastSearch + "\\E"; 
				Pattern pattern = Pattern.compile(s, Pattern.CASE_INSENSITIVE);
				for (int i = 0; i < cells.length; i++) {
					Matcher m = pattern.matcher(cells[i].getText());
					if (m.find() && (m.start() < iEarliest || iEarliest == -1)) {
						iEarliest = m.start();
						index = i;
					}
				}

				if (index < 0)
					// Insertion Point (best guess)
					index = -1 * index - 1;
			}

			if (index >= 0) {
				if (index >= cells.length)
					index = cells.length - 1;
				TableRowCore row = cells[index].getTableRowCore();
				int iTableIndex = row.getIndex();
				if (iTableIndex >= 0) {
					tv.setSelectedRows(new TableRowCore[] { row });
				}
			}
			lLastSearchTime = System.currentTimeMillis();
			updateTableLabel();
		}
		e.doit = false;
	}

	private void openFilterDialog() {
		InputShell is = new InputShell("MyTorrentsView.dialog.setFilter.title",
				"MyTorrentsView.dialog.setFilter.text");
		is.setTextValue(sLastSearch);
		is.setLabelParameters(new String[] { MessageText.getString(tv.getTableID() + "View"
				+ ".header")
		});

		String sReturn = is.open();
		if (sReturn == null)
			return;
		
		sLastSearch = sReturn;
		updateLastSearch();
	}
	
	private void updateLastSearch() {
		if (lblHeader == null || lblHeader.isDisposed())
			createTabs();

		if (txtFilter != null && !txtFilter.isDisposed()) {
			txtFilter.setVisible(true);
			lblX.setVisible(true);
			lblFilter.setVisible(true);

			if (!sLastSearch.equals(txtFilter.getText())) { 
				txtFilter.setText(sLastSearch);
				txtFilter.setSelection(sLastSearch.length());
			}

				if (bRegexSearch) {
				try {
					Pattern.compile(sLastSearch, Pattern.CASE_INSENSITIVE);
					txtFilter.setBackground(Colors.colorAltRow);
					Messages.setLanguageTooltip(txtFilter,
							"MyTorrentsView.filter.tooltip");
				} catch (Exception e) {
					txtFilter.setBackground(Colors.colorErrorBG);
					txtFilter.setToolTipText(e.getMessage());
				}
			} else {
				txtFilter.setBackground(null);
				Messages.setLanguageTooltip(txtFilter, "MyTorrentsView.filter.tooltip");
			}
		}
		if (lblX != null && !lblX.isDisposed()) {

			String oldID = (String) lblX.getData("ImageID");
      String id = sLastSearch.length() > 0 ? "smallx" : "smallx-gray";
      if (oldID == null || !oldID.equals(id)) {
      	ImageLoader.getInstance().setLabelImage(lblX, id);
      	lblX.setData("ImageID", id);
      }
		}

		if (searchUpdateEvent != null) {
			searchUpdateEvent.cancel();
		}
		searchUpdateEvent = SimpleTimer.addEvent("SearchUpdate",
				SystemTime.getOffsetTime(ASYOUTYPE_UPDATEDELAY),
				new TimerEventPerformer() {
					public void perform(TimerEvent event) {
						if (searchUpdateEvent.isCancelled()) {
							searchUpdateEvent = null;
							return;
						}
						searchUpdateEvent = null;
						activateCategory(currentCategory);
					}
				});
	}

	public void keyReleased(KeyEvent e) {
		// ignore
	}





  private void moveSelectedTorrentsDown() {
    // Don't use runForSelectDataSources to ensure the order we want
    Object[] dataSources = tv.getSelectedDataSources();
    Arrays.sort(dataSources, new Comparator() {
      public int compare (Object a, Object b) {
        return ((DownloadManager)a).getPosition() - ((DownloadManager)b).getPosition();
      }
    });
    for (int i = dataSources.length - 1; i >= 0; i--) {
      DownloadManager dm = (DownloadManager)dataSources[i];
      if (dm.getGlobalManager().isMoveableDown(dm)) {
        dm.getGlobalManager().moveDown(dm);
      }
    }

    boolean bForceSort = tv.getSortColumn().getName().equals("#");
    tv.columnInvalidate("#");
    tv.refreshTable(bForceSort);
  }

  private void moveSelectedTorrentsUp() {
    // Don't use runForSelectDataSources to ensure the order we want
    Object[] dataSources = tv.getSelectedDataSources();
    Arrays.sort(dataSources, new Comparator() {
      public int compare (Object a, Object b) {
        return ((DownloadManager)a).getPosition() - ((DownloadManager)b).getPosition();
      }
    });
    for (int i = 0; i < dataSources.length; i++) {
      DownloadManager dm = (DownloadManager)dataSources[i];
      if (dm.getGlobalManager().isMoveableUp(dm)) {
        dm.getGlobalManager().moveUp(dm);
      }
    }

    boolean bForceSort = tv.getSortColumn().getName().equals("#");
    tv.columnInvalidate("#");
    tv.refreshTable(bForceSort);
  }

	private void moveSelectedTorrents(int by) {
		// Don't use runForSelectDataSources to ensure the order we want
		Object[] dataSources = tv.getSelectedDataSources();
		if (dataSources.length <= 0)
			return;

		int[] newPositions = new int[dataSources.length];

		if (by < 0) {
			Arrays.sort(dataSources, new Comparator() {
				public int compare(Object a, Object b) {
					return ((DownloadManager) a).getPosition()
							- ((DownloadManager) b).getPosition();
				}
			});
		} else {
			Arrays.sort(dataSources, new Comparator() {
				public int compare(Object a, Object b) {
					return ((DownloadManager) b).getPosition()
							- ((DownloadManager) a).getPosition();
				}
			});
		}

		int count = globalManager.downloadManagerCount(isSeedingView); 
		for (int i = 0; i < dataSources.length; i++) {
			DownloadManager dm = (DownloadManager) dataSources[i];
			int pos = dm.getPosition() + by;
			if (pos < i + 1)
				pos = i + 1;
			else if (pos > count - i)
				pos = count - i;

			newPositions[i] = pos;
		}

		for (int i = 0; i < dataSources.length; i++) {
			DownloadManager dm = (DownloadManager) dataSources[i];
			globalManager.moveTo(dm, newPositions[i]);
		}

    boolean bForceSort = tv.getSortColumn().getName().equals("#");
    tv.columnInvalidate("#");
    tv.refreshTable(bForceSort);
	}

  private void moveSelectedTorrentsTop() {
    moveSelectedTorrentsTopOrEnd(true);
  }

  private void moveSelectedTorrentsEnd() {
    moveSelectedTorrentsTopOrEnd(false);
  }

  private void moveSelectedTorrentsTopOrEnd(boolean moveToTop) {
  	Object[] datasources = tv.getSelectedDataSources();
    if (datasources.length == 0)
      return;
  	DownloadManager[] downloadManagers = new DownloadManager[datasources.length];
  	System.arraycopy(datasources, 0, downloadManagers, 0, datasources.length);

    if(moveToTop)
      globalManager.moveTop(downloadManagers);
    else
      globalManager.moveEnd(downloadManagers);

    boolean bForceSort = tv.getSortColumn().getName().equals("#");
    if (bForceSort) {
    	tv.columnInvalidate("#");
    	tv.refreshTable(bForceSort);
    }
  }

  /**
   * @param parameterName the name of the parameter that has changed
   * @see org.gudy.azureus2.core3.config.ParameterListener#parameterChanged(java.lang.String)
   */
  public void parameterChanged(String parameterName) {
		if (parameterName == null
				|| parameterName.equals("DND Always In Incomplete")) {
			bDNDalwaysIncomplete = COConfigurationManager.getBooleanParameter("DND Always In Incomplete");
		}
	}

  private boolean top,bottom,up,down,run,start,stop,remove;

  private void computePossibleActions() {
    Object[] dataSources = tv.getSelectedDataSources();
    // enable up and down so that we can do the "selection rotate trick"
    up = down = run =  remove = (dataSources.length > 0);
    top = bottom = start = stop = false;
    for (int i = 0; i < dataSources.length; i++) {
      DownloadManager dm = (DownloadManager)dataSources[i];

      if(!start && ManagerUtils.isStartable(dm))
        start =  true;
      if(!stop && ManagerUtils.isStopable(dm))
        stop = true;
      if(!top && dm.getGlobalManager().isMoveableUp(dm))
        top = true;
      if(!bottom && dm.getGlobalManager().isMoveableDown(dm))
        bottom = true;
    }
  }

  public boolean isEnabled(String itemKey) {
    if(itemKey.equals("run"))
      return run;
    if(itemKey.equals("start"))
      return start;
    if(itemKey.equals("stop"))
      return stop;
    if(itemKey.equals("remove"))
      return remove;
    if(itemKey.equals("top"))
      return top;
    if(itemKey.equals("bottom"))
      return bottom;
    if(itemKey.equals("up"))
      return up;
    if(itemKey.equals("down"))
      return down;
    if(itemKey.equals("share"))
      return remove;
    return super.isEnabled(itemKey);
  }

  public void itemActivated(String itemKey) {
    if(itemKey.equals("top")) {
      moveSelectedTorrentsTop();
      return;
    }
    if(itemKey.equals("bottom")){
      moveSelectedTorrentsEnd();
      return;
    }
    if(itemKey.equals("up")) {
      moveSelectedTorrentsUp();
      return;
    }
    if(itemKey.equals("down")){
      moveSelectedTorrentsDown();
      return;
    }
    if(itemKey.equals("run")){
      TorrentUtil.runTorrents(tv.getSelectedDataSources());
      return;
    }
    if(itemKey.equals("start")){
      TorrentUtil.queueTorrents(tv.getSelectedDataSources(), cTablePanel.getShell());
      return;
    }
    if(itemKey.equals("stop")){
      TorrentUtil.stopTorrents(tv.getSelectedDataSources(), cTablePanel.getShell());
      return;
    }
    if(itemKey.equals("remove")){
      TorrentUtil.removeTorrents(tv.getSelectedDataSources(), cTablePanel.getShell());
      return;
    }
    super.itemActivated(itemKey);
  }
  

  // categorymanagerlistener Functions
  public void downloadManagerAdded(Category category, final DownloadManager manager)
  {
  	if (isOurDownloadManager(manager)) {
      tv.addDataSource(manager);
    }
  }

  public void downloadManagerRemoved(Category category, DownloadManager removed)
  {
    tv.removeDataSource(removed);
  }


  // DownloadManagerListener Functions
  public void stateChanged(DownloadManager manager, int state) {
    final TableRowCore row = tv.getRow(manager);
    if (row != null) {
    	Utils.execSWTThreadLater(0, new AERunnable() {
				public void runSupport() {
		    	row.refresh(true);
		    	if (row.isSelected()) {
		    		refreshIconBar();
		    	}
				}
    	});
    }
  }

  // DownloadManagerListener
  public void positionChanged(DownloadManager download, int oldPosition, int newPosition) {
  	if (isOurDownloadManager(download)) {
    	Utils.execSWTThreadLater(0, new AERunnable() {
				public void runSupport() {
					refreshIconBar();
				}
    	});
  	}
  }
  
  // DownloadManagerListener
  public void filePriorityChanged(DownloadManager download,
			DiskManagerFileInfo file) {
	}

  // DownloadManagerListener
	public void completionChanged(final DownloadManager manager,
			boolean bCompleted) {
		// manager has moved lists

		if (isOurDownloadManager(manager)) {

			// only make the download visible if it satisfies the category selection

			if (currentCategory == null
					|| currentCategory.getType() == Category.TYPE_ALL) {

				tv.addDataSource(manager);

			} else {

				int catType = currentCategory.getType();

				Category manager_category = manager.getDownloadState().getCategory();

				if (manager_category == null) {

					if (catType == Category.TYPE_UNCATEGORIZED) {

						tv.addDataSource(manager);
					}
				} else {

					if (currentCategory.getName().equals(manager_category.getName()))

						tv.addDataSource(manager);
				}
			}
		} else if ((isSeedingView && !bCompleted) || (!isSeedingView && bCompleted)) {

			tv.removeDataSource(manager);
		}
	}

  // DownloadManagerListener
  public void downloadComplete(DownloadManager manager) {
  }


  /**
   * Rebuild the table based on the category activated
   * 
   * @param category
   */
  private void activateCategory(Category category) {
		if (category != currentCategory) {
			if (currentCategory != null)
				currentCategory.removeCategoryListener(this);
			if (category != null)
				category.addCategoryListener(this);

			currentCategory = category;
		}
		
		Object[] managers = globalManager.getDownloadManagers().toArray();
		List list = Arrays.asList(tv.getDataSources());
		List listRemoves = new ArrayList();
		List listAdds = new ArrayList();
		
		for (int i = 0; i < managers.length; i++) {
			DownloadManager dm = (DownloadManager) managers[i];
		
			boolean bHave = list.contains(dm);
			if (!isOurDownloadManager(dm)) {
				if (bHave) {
					listRemoves.add(dm);
				}
			} else {
				if (!bHave) {
					listAdds.add(dm);
				}
			}
		}
		tv.removeDataSources(listRemoves.toArray());
		tv.addDataSources(listAdds.toArray());
		
  	tv.processDataSourceQueue();
		//tv.refreshTable(false);
	}
  
  public boolean isInCurrentCategory(DownloadManager manager) {
  	return isInCategory(manager, currentCategory);
  }

  private boolean isInCategory(DownloadManager manager, Category category) {
  	if (category == null) {
  		return true;
  	}
		int type = category.getType();
		if (type == Category.TYPE_ALL) {
			return true;
		}

  	Category dmCategory = manager.getDownloadState().getCategory();
  	if (dmCategory == null) {
  		return type == Category.TYPE_UNCATEGORIZED;
  	}
  	
  	return category.equals(dmCategory);
  }


  // CategoryManagerListener Functions
  public void categoryAdded(Category category) {
  	Utils.execSWTThread(
	  		new AERunnable() 
			{
	  			public void 
				runSupport() 
	  			{
	  				createTabs();
	  			}
			});
  }

  public void categoryRemoved(Category category) {
  	Utils.execSWTThread(
	  		new AERunnable() 
			{
	  			public void 
				runSupport() 
	  			{
	  				createTabs();
	  			}
			});
  }

  // globalmanagerlistener Functions
  public void downloadManagerAdded( DownloadManager dm ) {
    dm.addListener( this );
    downloadManagerAdded(null, dm);
  }

  public void downloadManagerRemoved( DownloadManager dm ) {
    dm.removeListener( this );
    DownloadBar.close(dm);
    downloadManagerRemoved(null, dm);
  }

  public void destroyInitiated() {  }
  public void destroyed() { }
  public void seedingStatusChanged( boolean seeding_only_mode, boolean b ){}       

  // End of globalmanagerlistener Functions
  


  // @see com.aelitis.azureus.ui.common.table.TableCountChangeListener#rowAdded(com.aelitis.azureus.ui.common.table.TableRowCore)
  public void rowAdded(TableRowCore row) {
		updateTableLabel();
  }

  // @see com.aelitis.azureus.ui.common.table.TableCountChangeListener#rowRemoved(com.aelitis.azureus.ui.common.table.TableRowCore)
  public void rowRemoved(TableRowCore row) {
		updateTableLabel();
	}
	

	public void updateLanguage() {
		super.updateLanguage();
		updateTableLabel();
		getComposite().layout(true, true);
	}

	/**
	 * 
	 */
	private boolean refreshingTableLabel = false;
	private void updateTableLabel() {
		if (refreshingTableLabel || lblHeader == null || lblHeader.isDisposed()) {
			return;
		}
		refreshingTableLabel = true;
		lblHeader.getDisplay().asyncExec(new AERunnable() {
			public void runSupport() {
				try {
					if (lblHeader != null && !lblHeader.isDisposed()) {
						String sText = MessageText.getString(tv.getTableID() + "View"
								+ ".header")
								+ " (" + tv.size(true) + ")";
						lblHeader.setText(sText);
						lblHeader.getParent().layout();
					}
				} finally {
					refreshingTableLabel = false;
				}
			}
		});
	}

	public boolean isTableFocus() {
		return viewActive;
		//return tv.isTableFocus();
	}
	
	public Image obfusticatedImage(final Image image, Point shellOffset) {
		return tv.obfusticatedImage(image, shellOffset);
	}
	
	/**
	 * Creates and return an <code>TableViewSWT</code>
	 * Subclasses my override to return a different TableViewSWT if needed
	 * @param basicItems
	 * @return
	 */
	protected TableViewSWT createTableView(String tableID,
			TableColumnCore[] basicItems) {
		int tableExtraStyle = COConfigurationManager.getIntParameter("MyTorrentsView.table.style");
		return new TableViewSWTImpl(tableID, "MyTorrentsView", basicItems, "#",
				tableExtraStyle | SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL);
	}

	/**
	 * Returns the default row height for the table
	 * Subclasses my override to return a different height if needed; a height of -1 means use default
	 * @return
	 */
	protected int getRowDefaultHeight(){
		return -1;
	}
}
