/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.dialog;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.MenuManager;
import net.sourceforge.rssowl.controller.MessageBoxFactory;
import net.sourceforge.rssowl.controller.NewsTabFolder;
import net.sourceforge.rssowl.controller.thread.FeedSearchManager;
import net.sourceforge.rssowl.dao.Exporter;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Channel;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.i18n.Dictionary;
import net.sourceforge.rssowl.util.search.SearchDefinition;
import net.sourceforge.rssowl.util.shop.FileShop;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.ProxyShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
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.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
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.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;

import java.io.IOException;
import java.util.Hashtable;

/**
 * Class displays a Dialog prompting for a topic. A "search"-button calls the
 * FeedSearchManager to search on the topic for RSS & RDF feeds. Any feeds that
 * are found are displayed in a table. Each feed may be opened in the tabfolder
 * with a doubleclick. A popup menuStructure is set to copy the XML Url.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class FeedSearchDialog {

  /** Height of the dialog in DLUs */
  private static final int DIALOG_HEIGHT = 300;

  /** Width of the dialog in DLUs */
  private static final int DIALOG_WIDTH = 450;

  private Thread animator;
  private Composite composite;
  private Button importButton;
  private String message;
  private Shell parent;
  private String title;
  MenuItem addFeedToFav;
  Button clearButton;
  TableColumn columnFormat;
  TableColumn columnTitle;
  TableColumn columnUrl;
  MenuItem copyXmlLocation;
  Button exportToOpml;
  Button intensiveSearchButton;
  boolean isIntensiveSearch;
  boolean isNativeLangSearch;
  CLabel messageLabel;
  Button nativeLangSearchButton;
  MenuItem openFeed;
  Hashtable results;
  Table resultTable;
  FeedSearchManager rssOwlFeedSearchManager;
  GUI rssOwlGui;
  Button searchButton;
  boolean searching;
  Text searchTopic;
  Shell shell;
  DragSource tableDragSource;
  TextTransfer textTransfer;

  /**
   * Creates an input dialog with Search, Stop, OK and Cancel buttons. Prompts
   * for a topic to search for.
   * 
   * @param dialogTitle the dialog title
   * @param dialogMessage the dialog message
   * @param rssOwlGui The RSSOwl Maincontroller
   */
  public FeedSearchDialog(String dialogTitle, String dialogMessage, GUI rssOwlGui) {
    this.title = dialogTitle;
    this.rssOwlGui = rssOwlGui;
    isIntensiveSearch = false;
    isNativeLangSearch = false;
    results = new Hashtable();
    searching = false;
    parent = GUI.shell;
    message = dialogMessage;
    textTransfer = TextTransfer.getInstance();
    createDialogArea();
  }

  /**
   * Display a new line in the table
   * 
   * @param url URL of the newsfeed
   * @param feedtitle Title of the newsfeed
   * @param format Format of the newsfeed
   */
  public void addResultElement(String url, String feedtitle, String format) {

    /** The user may have closed the dialog already! */
    if (!resultTable.isDisposed()) {
      if (!StringShop.isset(feedtitle))
        feedtitle = GUI.i18n.getTranslation("NO_TITLE");

      /** Create a new Entry to the Table */
      TableItem tableItem = new TableItem(resultTable, SWT.NONE);
      tableItem.setText(0, feedtitle);

      if (StringShop.isset(url))
        tableItem.setText(1, url);

      if (StringShop.isset(format))
        tableItem.setText(2, format);

      /** Do not check if Favorite already present */
      boolean check = StringShop.isset(url) && !Category.linkExists(url);
      tableItem.setChecked(check);

      /** Save this result into the Hashtable */
      if (!results.containsKey(url))
        results.put(url, feedtitle);

      /** Enable popup and buttons */
      setResultControlsEnabled(true);
    }
  }

  /**
   * Get the results from the feed searches
   * 
   * @return Returns the results.
   */
  public Hashtable getResults() {
    return results;
  }

  /**
   * @return Returns TRUE if a search is currently runnung.
   */
  public boolean isSearching() {
    return searching;
  }

  /** Display the dialog */
  public void open() {
    WidgetShop.openDialogsCount++;
    shell.open();
  }

  /**
   * Set the state of the search- and stop button
   * 
   * @param searching TRUE if a search is performed
   */
  public void setButtonState(boolean searching) {

    /** Set new text and data to the button */
    if (GUI.isAlive() && !searchButton.isDisposed()) {
      searchButton.setText(GUI.i18n.getTranslation((searching == true) ? "BUTTON_STOP_SEARCH" : "BUTTON_SEARCH"));
      searchButton.setData((searching == true) ? "BUTTON_STOP_SEARCH" : "BUTTON_SEARCH");
    }

    /** Update Layout */
    if (GUI.isAlive() && !searchButton.isDisposed() && !searchButton.getParent().isDisposed()) {
      searchButton.update();
      searchButton.getParent().layout(true);
    }
  }

  /**
   * Set / unset the warning message on the dialog
   * 
   * @param errorMessage The warning message or NULL for no warning
   */
  public void setErrorMessage(String errorMessage) {

    /** The user may have closed the dialog already! */
    if (!messageLabel.isDisposed()) {
      messageLabel.setImage(errorMessage == null ? null : PaintShop.iconError);
      messageLabel.setText(errorMessage == null ? "" : errorMessage);
    }
  }

  /**
   * Set a message to the messageLabel and the search image
   * 
   * @param message The message
   */
  public void setMessage(String message) {

    /** The user may have closed the dialog already! */
    if (!messageLabel.isDisposed()) {
      messageLabel.setText(message);
      messageLabel.setImage(PaintShop.iconSearch);
    }
  }

  /**
   * Set wether a search is currently running.
   * 
   * @param searching TRUE if a search is currently running.
   */
  public void setSearching(boolean searching) {
    this.searching = searching;
  }

  /** Stop the animation on the status message */
  public void stopStatusMessageAnimate() {
    if (animator != null && !animator.isInterrupted())
      animator.interrupt();
  }

  /** Create the dialog components */
  private void createDialogArea() {

    /** Shell for the dialog */
    shell = new Shell(parent, SWT.CLOSE | SWT.TITLE | SWT.MIN | SWT.MAX | SWT.RESIZE);
    shell.setLayout(new FillLayout());

    /** On Mac do not set Shell Image since it will change the Dock Image */
    if (!GlobalSettings.isMac())
      shell.setImage(PaintShop.iconSearch);

    shell.setText(title);
    shell.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {

        /** Counter was already updated */
        if (!shell.getMinimized())
          WidgetShop.openDialogsCount--;

        /** Stop search threads when disposing the dialog */
        if (rssOwlFeedSearchManager != null && rssOwlFeedSearchManager.isRunning())
          rssOwlFeedSearchManager.stopSearch();

        /** Stop animation if running */
        stopStatusMessageAnimate();

        /** Reset flag */
        setSearching(false);
      }
    });

    /** Update Counter for open dialogs */
    shell.addShellListener(new ShellAdapter() {
      public void shellDeiconified(ShellEvent e) {
        WidgetShop.openDialogsCount++;
      }

      public void shellIconified(ShellEvent e) {
        WidgetShop.openDialogsCount--;
      }
    });

    /** Using composite for the components */
    composite = new Composite(shell, SWT.NONE);
    composite.setLayout(LayoutShop.createGridLayout(2, 0, 5));

    /** Dialog Message Holder */
    Composite dialogMessageHolder = new Composite(composite, SWT.NONE);
    dialogMessageHolder.setLayout(LayoutShop.createGridLayout(1, 5, 0));
    dialogMessageHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Dialog Message */
    Label dialogMessage = new Label(dialogMessageHolder, SWT.WRAP);
    dialogMessage.setText(message + ":");
    dialogMessage.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    dialogMessage.setFont(FontShop.dialogFont);

    /** Hold the search components */
    Composite searchHolder = new Composite(composite, SWT.NONE);
    searchHolder.setLayout(LayoutShop.createGridLayout(2, 5, 0));
    searchHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Input to enter a search topic */
    searchTopic = new Text(searchHolder, SWT.SINGLE | SWT.BORDER);
    searchTopic.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    searchTopic.setFont(FontShop.dialogFont);
    searchTopic.setFocus();
    searchTopic.addKeyListener(new KeyAdapter() {

      /** Listen on "Enter" key */
      public void keyPressed(KeyEvent e) {

        /** Only perform search if a search is not running currently */
        if (e.character == SWT.CR && !searchTopic.getText().equals("") && searchButton.getData().equals("BUTTON_SEARCH"))
          performSearch(searchTopic.getText());
      }
    });

    /** Set search button enabled / disabled in dependance to searchWebsite isset */
    searchTopic.addModifyListener(new ModifyListener() {
      public void modifyText(ModifyEvent e) {

        /** Only set state if a search is not running currently */
        if (searchButton.getData().equals("BUTTON_SEARCH"))
          searchButton.setEnabled(!searchTopic.getText().trim().equals(""));
      }
    });

    /** Add Drop Support and run Discovery on Drop */
    WidgetShop.setupDropSupport(searchTopic, new Runnable() {
      public void run() {
        if (StringShop.isset(searchTopic.getText()) && !isSearching())
          performSearch(searchTopic.getText().trim());
      }
    });

    /** Tweak Text Widget */
    WidgetShop.tweakTextWidget(searchTopic);

    /** Perform the search after click */
    searchButton = new Button(searchHolder, SWT.NONE);
    searchButton.setText(GUI.i18n.getTranslation("BUTTON_SEARCH"));
    searchButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    searchButton.setFont(FontShop.dialogFont);
    searchButton.setEnabled(false);
    searchButton.setData("BUTTON_SEARCH");
    searchButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** A search is not running, start one on click */
        if (searchButton.getData().equals("BUTTON_SEARCH"))
          performSearch(searchTopic.getText());

        /** A search is running, stop it on click */
        else
          stopSearch();
      }
    });

    /** Make Search Button the default button of the dialog */
    shell.setDefaultButton(searchButton);

    /** Holds options intensive search and native language search */
    Composite searchOptionsHolder = new Composite(composite, SWT.NONE);
    searchOptionsHolder.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
    searchOptionsHolder.setLayout(LayoutShop.createGridLayout(2, 5, 0, 5, 20, false));

    /** Check for the intensive search */
    intensiveSearchButton = new Button(searchOptionsHolder, SWT.CHECK);
    intensiveSearchButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
    intensiveSearchButton.setSelection(isIntensiveSearch);
    intensiveSearchButton.setFont(FontShop.dialogFont);
    intensiveSearchButton.setText(GUI.i18n.getTranslation("LABEL_INTENSIVE_SEARCH"));
    intensiveSearchButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        isIntensiveSearch = intensiveSearchButton.getSelection();
      }
    });

    /** Check for the native lang search */
    nativeLangSearchButton = new Button(searchOptionsHolder, SWT.CHECK);
    nativeLangSearchButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
    nativeLangSearchButton.setSelection(isNativeLangSearch);
    nativeLangSearchButton.setText(GUI.i18n.getTranslation("LABEL_PREFERED_LANGUAGE") + ": " + GUI.i18n.getTranslation(Dictionary.selectedLanguage));
    nativeLangSearchButton.setFont(FontShop.dialogFont);
    nativeLangSearchButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        isNativeLangSearch = nativeLangSearchButton.getSelection();
      }
    });

    /** Provide a 5px margin around the Table */
    Composite tableHolderMargin = new Composite(composite, SWT.NONE);
    tableHolderMargin.setLayout(LayoutShop.createFillLayout(5, 0));
    tableHolderMargin.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_BOTH, 2));

    /** Holding the result table */
    final Composite tableHolder = new Composite(tableHolderMargin, SWT.NONE);

    /** Table to display the results of the search */
    resultTable = new Table(tableHolder, SWT.BORDER | SWT.FULL_SELECTION | SWT.CHECK | SWT.MULTI);
    resultTable.setLinesVisible(true);
    resultTable.setHeaderVisible(true);
    resultTable.setFont(FontShop.dialogFont);

    /** Allow to Drag results into the Favorites Tree */
    tableDragSource = new DragSource(resultTable, DND.DROP_MOVE | DND.DROP_COPY);
    tableDragSource.setTransfer(new Transfer[] { TextTransfer.getInstance() });
    tableDragSource.addDragListener(new DragSourceAdapter() {

      /** Reset the DragSourceItem field */
      public void dragFinished(DragSourceEvent event) {
        rssOwlGui.getRSSOwlFavoritesTree().getRSSOwlFavoritesTreeDND().setDragSourceItem(null);
      }

      /** Set Feed Title and URL into Data Slot */
      public void dragSetData(DragSourceEvent event) {
        TableItem item = (TableItem) rssOwlGui.getRSSOwlFavoritesTree().getRSSOwlFavoritesTreeDND().getDragSourceItem();
        if (WidgetShop.isset(item))
          event.data = item.getText(1) + System.getProperty("line.separator") + item.getText(0);
      }

      /** Dragging has begun */
      public void dragStart(DragSourceEvent event) {

        /** User is Dragging a Selection */
        if (resultTable.getSelectionCount() > 0) {
          TableItem item = resultTable.getSelection()[0];
          event.doit = true;

          /** Store the item in the DND class of the favorites tree */
          rssOwlGui.getRSSOwlFavoritesTree().getRSSOwlFavoritesTreeDND().setDragSourceItem(item);
        }

        /** User is not Dragging a Selection */
        else {
          event.doit = false;
        }
      }
    });

    /** Dispose DND on Table Dispose */
    resultTable.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        tableDragSource.dispose();
      }
    });

    /** Support some Hotkeys on the Table */
    resultTable.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {

        /** Select All on CTRL/CMD + A */
        if (((e.stateMask & SWT.CTRL) != 0 || (e.stateMask & SWT.COMMAND) != 0) && (e.keyCode == 'a' || e.keyCode == 'A'))
          resultTable.selectAll();

        /** Un-Check selection on DEL/BS */
        else if (e.keyCode == SWT.DEL || (GlobalSettings.isMac() && e.keyCode == SWT.BS)) {
          TableItem selection[] = resultTable.getSelection();
          for (int i = 0; i < selection.length; i++) {
            selection[i].setChecked(false);
          }
        }
      }
    });

    /** Listeners for the Table */
    resultTable.addMouseListener(new MouseAdapter() {
      public void mouseDoubleClick(MouseEvent e) {
        if (resultTable.getSelectionCount() > 0)
          openFeed(resultTable.getSelection()[0]);
      }
    });

    /** Column: Title of the newsfeed */
    columnTitle = new TableColumn(resultTable, SWT.LEFT);
    columnTitle.setText(GUI.i18n.getTranslation("TABLE_HEADER_FEEDTITLE"));

    /** Column: URL of the newsfeed */
    columnUrl = new TableColumn(resultTable, SWT.LEFT);
    columnUrl.setText(GUI.i18n.getTranslation("TABLE_HEADER_FEEDURL"));

    /** Column: Format of the newsfeed */
    columnFormat = new TableColumn(resultTable, SWT.LEFT);
    columnFormat.setText(GUI.i18n.getTranslation("CHANNEL_INFO_RSSVERSION"));

    /** Dynamically resize the TableColumns as the Dialog resizes */
    tableHolder.addControlListener(new ControlAdapter() {
      public void controlResized(ControlEvent e) {
        Rectangle area = tableHolder.getClientArea();
        int width = area.width - 2 * resultTable.getBorderWidth() - resultTable.getVerticalBar().getSize().x;

        /** Bug on Mac: Width is too big */
        if (GlobalSettings.isMac())
          width -= 30;

        Point oldSize = resultTable.getSize();
        if (oldSize.x > area.width) {
          columnTitle.setWidth((width / 6) * 3);
          columnUrl.setWidth((width / 6) * 2);
          columnFormat.setWidth((width / 6) * 1);
          resultTable.setSize(area.width, area.height);
        } else {
          resultTable.setSize(area.width, area.height);
          columnTitle.setWidth((width / 6) * 3);
          columnUrl.setWidth((width / 6) * 2);
          columnFormat.setWidth((width / 6) * 1);
        }
      }
    });

    /** Popup menue */
    Menu resultTableMenu = new Menu(resultTable);

    /** Update enabled state of items */
    resultTableMenu.addMenuListener(new MenuAdapter() {
      public void menuShown(MenuEvent e) {
        addFeedToFav.setEnabled(resultTable.getSelectionCount() > 0);
        openFeed.setEnabled(resultTable.getSelectionCount() > 0);
        copyXmlLocation.setEnabled(resultTable.getSelectionCount() > 0);
      }
    });

    /** Add to Favorites */
    addFeedToFav = new MenuItem(resultTableMenu, SWT.NONE);
    addFeedToFav.setText(GUI.i18n.getTranslation("BUTTON_ADDTO_FAVORITS") + "...");
    if (!GlobalSettings.isMac())
      addFeedToFav.setImage(PaintShop.iconAddToFavorites);
    addFeedToFav.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        TableItem selection[] = resultTable.getSelection();
        for (int i = 0; i < selection.length; i++)
          rssOwlGui.getEventManager().actionAddToFavorites(selection[i].getText(0), selection[i].getText(1));
      }
    });

    /** Separator */
    new MenuItem(resultTableMenu, SWT.SEPARATOR);

    /** Open selected feed in tab folder */
    openFeed = new MenuItem(resultTableMenu, SWT.NONE);
    openFeed.setText(GUI.i18n.getTranslation("BUTTON_OPEN"));
    openFeed.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        TableItem selection[] = resultTable.getSelection();
        for (int i = 0; i < selection.length; i++)
          openFeed(selection[i]);
      }
    });

    /** Separator */
    new MenuItem(resultTableMenu, SWT.SEPARATOR);

    /** Copy the XML location of the selected feed */
    copyXmlLocation = new MenuItem(resultTableMenu, SWT.NONE);
    copyXmlLocation.setText(GUI.i18n.getTranslation("POP_COPY_NEWS_URL"));
    copyXmlLocation.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (resultTable.getSelectionCount() > 0)
          rssOwlGui.getEventManager().getClipBoard().setContents(new Object[] { resultTable.getSelection()[0].getText(1) }, new Transfer[] { textTransfer });
      }
    });

    /** Init the Mnemonics */
    MenuManager.initMnemonics(resultTableMenu);

    /** Apply Menu */
    resultTable.setMenu(resultTableMenu);

    /** Label Holder */
    Composite dialogMessageLabelHolder = new Composite(composite, SWT.NONE);
    dialogMessageLabelHolder.setLayout(LayoutShop.createGridLayout(1, 5, 0));
    dialogMessageLabelHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Message Label */
    messageLabel = new CLabel(dialogMessageLabelHolder, SWT.NONE);
    messageLabel.setFont(FontShop.dialogFont);
    messageLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));

    Label sep = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
    sep.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Button holder */
    Composite buttonHolder = new Composite(composite, SWT.NONE);
    buttonHolder.setLayout(LayoutShop.createGridLayout(3, 5, 5, 5, 5, false));
    buttonHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Import results into category */
    importButton = new Button(buttonHolder, SWT.NONE);
    importButton.setFont(FontShop.dialogFont);
    importButton.setText(GUI.i18n.getTranslation("POP_IMPORT") + "...");
    importButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
    importButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        importResults();
      }
    });

    /** Export results to OPML */
    exportToOpml = new Button(buttonHolder, SWT.NONE);
    exportToOpml.setText(GUI.i18n.getTranslation("BUTTON_EXPORT_TO_OPML") + "...");
    exportToOpml.setFont(FontShop.dialogFont);
    exportToOpml.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
    exportToOpml.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        generateOpml();
      }
    });

    /** Clear result table */
    clearButton = new Button(buttonHolder, SWT.NONE);
    clearButton.setText(GUI.i18n.getTranslation("BUTTON_CLEAR_RESULTS"));
    clearButton.setFont(FontShop.dialogFont);
    clearButton.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
    clearButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        resultTable.removeAll();
        results.clear();

        /** Disable result controls */
        setResultControlsEnabled(false);
      }
    });

    /** Disable result controls */
    setResultControlsEnabled(false);

    /** Set Size calculating with Dialog Font and DLUs */
    GC gc = new GC(shell);
    gc.setFont(FontShop.dialogFont);
    FontMetrics fontMetrics = gc.getFontMetrics();
    int width = Dialog.convertHorizontalDLUsToPixels(fontMetrics, DIALOG_WIDTH);
    int height = Dialog.convertVerticalDLUsToPixels(fontMetrics, DIALOG_HEIGHT);
    shell.setSize(width, height);
    gc.dispose();

    /** Position shell */
    LayoutShop.positionShell(shell, false, WidgetShop.openDialogsCount);

    /** Set Mnemonics to Buttons */
    WidgetShop.initMnemonics(new Button[] { searchButton, intensiveSearchButton, nativeLangSearchButton, importButton, exportToOpml, clearButton });
  }

  /** Start a thread to animate the status message */
  private void startStatusMessageAnimate() {
    animator = new Thread() {
      public void run() {
        while (!isInterrupted()) {

          /** Set points every 300ms */
          try {
            for (int i = 1; i < 6; i++) {
              sleep(300);
              setStatusPoints(i);
            }
          } catch (InterruptedException e) {
            return;
          }
        }
      }
    };
    animator.setName("Search Status Animator Thread");
    animator.setDaemon(true);
    animator.start();
  }

  /** Generate an OPML XML from the results */
  void generateOpml() {
    TableItem items[] = resultTable.getItems();
    Hashtable results = new Hashtable();
    String topic = searchTopic.getText();

    /** This indicates the category of the results */
    if (topic.equals(""))
      topic = GUI.i18n.getTranslation("NO_TITLE");

    /** Fill all checked results into the results Hashtable */
    for (int i = 0; i < items.length; i++) {
      if (items[i].getChecked())
        results.put(items[i].getText(1), items[i].getText(0));
    }

    /** Export results to OPML */
    try {
      new Exporter().exportResultsToOPML(results, topic);
      FileShop.exportCategory(shell, topic.replaceAll(" ", "_") + ".opml", new String[] { "*.opml", "*.xml", "*.*" });
    } catch (IOException e) {
      MessageBoxFactory.showError(shell, e);
    }
  }

  /**
   * Import results from the search into a category
   */
  void importResults() {

    /** Open a category dialog for the user to select a category */
    SelectCategoryDialog selectCategory = new SelectCategoryDialog(shell, GUI.i18n.getTranslation("MESSAGEBOX_SELECT_CAT"));

    /** User has canceld the dialog, return */
    if (selectCategory.open() != Window.OK)
      return;

    /** Obtain selected category path */
    String categoryPath = selectCategory.getCatPath();

    /** User has not selected a category, return */
    if (!StringShop.isset(categoryPath))
      return;

    /** Obtain model object of selected category */
    Category selectedCategory = rssOwlGui.getRSSOwlFavoritesTree().getSelectedCat(categoryPath);

    /** Foreach selected newsfeed of the search results */
    TableItem items[] = resultTable.getItems();
    boolean buildTree = false;

    /** Add as new favorite */
    for (int i = 0; i < items.length; i++) {
      if (items[i].getChecked()) {
        buildTree = true;
        String url = items[i].getText(1);
        String title = items[i].getText(0);

        /** Create Favorite to be used to clone Meta Data Information */
        Favorite favorite = new Favorite(url, title, selectedCategory);
        favorite.setCreationDate(System.currentTimeMillis());
        favorite.setLastVisitDate(0);
        favorite.setUseProxy(ProxyShop.isUseProxy());
        Channel channel = rssOwlGui.getFeedCacheManager().getCachedNewsfeed(url);
        if (channel != null) {
          favorite.syncMetaData(channel);
          favorite.setUnreadNewsCount(channel.getUnreadNewsCount());
        }

        /** Only add if the category is not yet containing that feed */
        if (!selectedCategory.getFavorites().containsKey(url)) {

          /** Add favorite into favorites tree */
          rssOwlGui.getRSSOwlFavoritesTree().addFavorite(selectedCategory, url, title, favorite);
        }
      }
    }

    /** Rebuild favorites tree */
    if (buildTree)
      rssOwlGui.getRSSOwlFavoritesTree().buildFavoritesTree();
  }

  /**
   * Open selected item as newsfeed in the tabfolder
   * 
   * @param item TableItem holding URL of the newsfeed
   */
  void openFeed(TableItem item) {
    rssOwlGui.loadNewsFeed(item.getText(1), SearchDefinition.NO_SEARCH, true, true, NewsTabFolder.DISPLAY_MODE_FOCUS);
  }

  /**
   * Begin the search
   * 
   * @param topic Topic to search for
   */
  void performSearch(String topic) {
    if (!topic.equals("")) {

      /** Update flag */
      setSearching(true);

      /** Reset buttons */
      setButtonState(true);
      setErrorMessage(null);

      /** Set search is running message */
      messageLabel.setText(GUI.i18n.getTranslation("LABEL_SEARCH_RUNNING"));
      messageLabel.setImage(PaintShop.iconSearch);

      /** Animate message label */
      startStatusMessageAnimate();

      /** Encode topic to use as URL */
      rssOwlFeedSearchManager = new FeedSearchManager(topic, this, isIntensiveSearch, isNativeLangSearch);
      rssOwlFeedSearchManager.startSearch();
    }
  }

  /**
   * Enable / Disable controls that are used when results are available.
   * 
   * @param enabled TRUE if enabled
   */
  void setResultControlsEnabled(boolean enabled) {
    addFeedToFav.setEnabled(enabled);
    openFeed.setEnabled(enabled);
    copyXmlLocation.setEnabled(enabled);
    clearButton.setEnabled(enabled);
    exportToOpml.setEnabled(enabled);
    importButton.setEnabled(enabled);
  }

  /**
   * Set a number of points to the status message
   * 
   * @param number The number of points
   */
  void setStatusPoints(final int number) {

    /** Only perform this Runnable if RSSOwl was not closed */
    if (GUI.isAlive()) {
      GUI.display.asyncExec(new Runnable() {
        public void run() {
          String points = "";

          /** Create String with points */
          for (int i = 0; i < number; i++)
            points += ".";

          /** Set animated message, if message label and parent is not disposed */
          if (!messageLabel.isDisposed() && !messageLabel.getParent().isDisposed())
            messageLabel.setText(GUI.i18n.getTranslation("LABEL_SEARCH_RUNNING") + points);
        }
      });
    }
  }

  /**
   * Stop the running search
   */
  void stopSearch() {
    setSearching(false);

    /** Stop Feed Search Manager if set */
    if (rssOwlFeedSearchManager != null)
      rssOwlFeedSearchManager.stopSearch();

    stopStatusMessageAnimate();
    setButtonState(false);
    setMessage(GUI.i18n.getTranslation("LABEL_SEARCH_FINISHED"));
  }
}