/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * $Id: mainwin.cc 14098 2013-06-26 01:48:47Z jordan $
 */

#include <cassert>
#include <iostream>

#include <QtGui>

#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>

#include "about.h"
#include "add-data.h"
#include "app.h"
#include "details.h"
#include "filterbar.h"
#include "filters.h"
#include "formatter.h"
#include "hig.h"
#include "mainwin.h"
#include "make-dialog.h"
#include "options.h"
#include "prefs.h"
#include "prefs-dialog.h"
#include "relocate.h"
#include "session.h"
#include "session-dialog.h"
#include "speed.h"
#include "stats-dialog.h"
#include "torrent-delegate.h"
#include "torrent-delegate-min.h"
#include "torrent-filter.h"
#include "torrent-model.h"
#include "triconpushbutton.h"
#include "ui_mainwin.h"

#define PREFS_KEY "prefs-key";


/**
 * This is a proxy-style for that forces it to be always disabled.
 * We use this to make our torrent list view behave consistently on
 * both GTK and Qt implementations.
 */
class ListViewProxyStyle: public QProxyStyle
{
  public:

    int styleHint (StyleHint            hint,
                   const QStyleOption * option = 0,
                   const QWidget      * widget = 0,
                   QStyleHintReturn   * returnData = 0) const
    {
      if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
        return 0;
      return QProxyStyle::styleHint (hint, option, widget, returnData);
    }
};


QIcon
TrMainWindow :: getStockIcon (const QString& name, int fallback)
{
  QIcon icon = QIcon::fromTheme (name);

  if (icon.isNull () && (fallback >= 0))
    icon = style ()->standardIcon (QStyle::StandardPixmap (fallback), 0, this);

  return icon;
}

namespace
{
  QSize calculateTextButtonSizeHint (QPushButton * button)
  {
    QStyleOptionButton opt;
    opt.initFrom (button);
    QString s (button->text ());
    if (s.isEmpty ())
      s = QString::fromLatin1 ("XXXX");
    QFontMetrics fm = button->fontMetrics ();
    QSize sz = fm.size (Qt::TextShowMnemonic, s);
    return button->style ()->sizeFromContents (QStyle::CT_PushButton, &opt, sz, button).expandedTo (QApplication::globalStrut ());
  }
}


TrMainWindow :: TrMainWindow (Session& session, Prefs& prefs, TorrentModel& model, bool minimized):
  myLastFullUpdateTime (0),
  mySessionDialog (new SessionDialog (session, prefs, this)),
  myPrefsDialog (0),
  myAboutDialog (new AboutDialog (this)),
  myStatsDialog (new StatsDialog (session, this)),
  myDetailsDialog (0),
  myFileDialogOptionsCheck (0),
  myFilterModel (prefs),
  myTorrentDelegate (new TorrentDelegate (this)),
  myTorrentDelegateMin (new TorrentDelegateMin (this)),
  mySession (session),
  myPrefs (prefs),
  myModel (model),
  mySpeedModeOffIcon (":/icons/alt-limit-off.png"),
  mySpeedModeOnIcon (":/icons/alt-limit-on.png"),
  myLastSendTime (0),
  myLastReadTime (0),
  myNetworkTimer (this),
  myRefreshTrayIconTimer (this),
  myRefreshActionSensitivityTimer (this)
{
  setAcceptDrops (true);

  QAction * sep = new QAction (this);
  sep->setSeparator (true);

  ui.setupUi (this);

  QStyle * style = this->style ();

  int i = style->pixelMetric (QStyle::PM_SmallIconSize, 0, this);
  const QSize smallIconSize (i, i);

  ui.listView->setStyle (new ListViewProxyStyle);

  // icons
  ui.action_OpenFile->setIcon (getStockIcon ("folder-open", QStyle::SP_DialogOpenButton));
  ui.action_New->setIcon (getStockIcon ("document-new", QStyle::SP_DesktopIcon));
  ui.action_Properties->setIcon (getStockIcon ("document-properties", QStyle::SP_DesktopIcon));
  ui.action_OpenFolder->setIcon (getStockIcon ("folder-open", QStyle::SP_DirOpenIcon));
  ui.action_Start->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
  ui.action_StartNow->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
  ui.action_Announce->setIcon (getStockIcon ("network-transmit-receive"));
  ui.action_Pause->setIcon (getStockIcon ("media-playback-pause", QStyle::SP_MediaPause));
  ui.action_Remove->setIcon (getStockIcon ("list-remove", QStyle::SP_TrashIcon));
  ui.action_Delete->setIcon (getStockIcon ("edit-delete", QStyle::SP_TrashIcon));
  ui.action_StartAll->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
  ui.action_PauseAll->setIcon (getStockIcon ("media-playback-pause", QStyle::SP_MediaPause));
  ui.action_Quit->setIcon (getStockIcon ("application-exit"));
  ui.action_SelectAll->setIcon (getStockIcon ("edit-select-all"));
  ui.action_ReverseSortOrder->setIcon (getStockIcon ("view-sort-ascending", QStyle::SP_ArrowDown));
  ui.action_Preferences->setIcon (getStockIcon ("preferences-system"));
  ui.action_Contents->setIcon (getStockIcon ("help-contents", QStyle::SP_DialogHelpButton));
  ui.action_About->setIcon (getStockIcon ("help-about"));
  ui.action_QueueMoveTop->setIcon (getStockIcon ("go-top"));
  ui.action_QueueMoveUp->setIcon (getStockIcon ("go-up", QStyle::SP_ArrowUp));
  ui.action_QueueMoveDown->setIcon (getStockIcon ("go-down", QStyle::SP_ArrowDown));
  ui.action_QueueMoveBottom->setIcon (getStockIcon ("go-bottom"));

  // ui signals
  connect (ui.action_Toolbar, SIGNAL (toggled (bool)), this, SLOT (setToolbarVisible (bool)));
  connect (ui.action_Filterbar, SIGNAL (toggled (bool)), this, SLOT (setFilterbarVisible (bool)));
  connect (ui.action_Statusbar, SIGNAL (toggled (bool)), this, SLOT (setStatusbarVisible (bool)));
  connect (ui.action_CompactView, SIGNAL (toggled (bool)), this, SLOT (setCompactView (bool)));
  connect (ui.action_SortByActivity, SIGNAL (toggled (bool)), this, SLOT (onSortByActivityToggled (bool)));
  connect (ui.action_SortByAge,      SIGNAL (toggled (bool)), this, SLOT (onSortByAgeToggled (bool)));
  connect (ui.action_SortByETA,      SIGNAL (toggled (bool)), this, SLOT (onSortByETAToggled (bool)));
  connect (ui.action_SortByName,     SIGNAL (toggled (bool)), this, SLOT (onSortByNameToggled (bool)));
  connect (ui.action_SortByProgress, SIGNAL (toggled (bool)), this, SLOT (onSortByProgressToggled (bool)));
  connect (ui.action_SortByQueue,    SIGNAL (toggled (bool)), this, SLOT (onSortByQueueToggled (bool)));
  connect (ui.action_SortByRatio,    SIGNAL (toggled (bool)), this, SLOT (onSortByRatioToggled (bool)));
  connect (ui.action_SortBySize,     SIGNAL (toggled (bool)), this, SLOT (onSortBySizeToggled (bool)));
  connect (ui.action_SortByState,    SIGNAL (toggled (bool)), this, SLOT (onSortByStateToggled (bool)));
  connect (ui.action_ReverseSortOrder, SIGNAL (toggled (bool)), this, SLOT (setSortAscendingPref (bool)));
  connect (ui.action_Start, SIGNAL (triggered ()), this, SLOT (startSelected ()));
  connect (ui.action_QueueMoveTop,    SIGNAL (triggered ()), this, SLOT (queueMoveTop ()));
  connect (ui.action_QueueMoveUp,     SIGNAL (triggered ()), this, SLOT (queueMoveUp ()));
  connect (ui.action_QueueMoveDown,   SIGNAL (triggered ()), this, SLOT (queueMoveDown ()));
  connect (ui.action_QueueMoveBottom, SIGNAL (triggered ()), this, SLOT (queueMoveBottom ()));
  connect (ui.action_StartNow, SIGNAL (triggered ()), this, SLOT (startSelectedNow ()));
  connect (ui.action_Pause, SIGNAL (triggered ()), this, SLOT (pauseSelected ()));
  connect (ui.action_Remove, SIGNAL (triggered ()), this, SLOT (removeSelected ()));
  connect (ui.action_Delete, SIGNAL (triggered ()), this, SLOT (deleteSelected ()));
  connect (ui.action_Verify, SIGNAL (triggered ()), this, SLOT (verifySelected ()));
  connect (ui.action_Announce, SIGNAL (triggered ()), this, SLOT (reannounceSelected ()));
  connect (ui.action_StartAll, SIGNAL (triggered ()), this, SLOT (startAll ()));
  connect (ui.action_PauseAll, SIGNAL (triggered ()), this, SLOT (pauseAll ()));
  connect (ui.action_OpenFile, SIGNAL (triggered ()), this, SLOT (openTorrent ()));
  connect (ui.action_AddURL, SIGNAL (triggered ()), this, SLOT (openURL ()));
  connect (ui.action_New, SIGNAL (triggered ()), this, SLOT (newTorrent ()));
  connect (ui.action_Preferences, SIGNAL (triggered ()), this, SLOT (openPreferences ()));
  connect (ui.action_Statistics, SIGNAL (triggered ()), myStatsDialog, SLOT (show ()));
  connect (ui.action_Donate, SIGNAL (triggered ()), this, SLOT (openDonate ()));
  connect (ui.action_About, SIGNAL (triggered ()), myAboutDialog, SLOT (show ()));
  connect (ui.action_Contents, SIGNAL (triggered ()), this, SLOT (openHelp ()));
  connect (ui.action_OpenFolder, SIGNAL (triggered ()), this, SLOT (openFolder ()));
  connect (ui.action_CopyMagnetToClipboard, SIGNAL (triggered ()), this, SLOT (copyMagnetLinkToClipboard ()));
  connect (ui.action_SetLocation, SIGNAL (triggered ()), this, SLOT (setLocation ()));
  connect (ui.action_Properties, SIGNAL (triggered ()), this, SLOT (openProperties ()));
  connect (ui.action_SessionDialog, SIGNAL (triggered ()), mySessionDialog, SLOT (show ()));

  connect (ui.listView, SIGNAL (activated (const QModelIndex&)), ui.action_Properties, SLOT (trigger ()));

  // signals
  connect (ui.action_SelectAll, SIGNAL (triggered ()), ui.listView, SLOT (selectAll ()));
  connect (ui.action_DeselectAll, SIGNAL (triggered ()), ui.listView, SLOT (clearSelection ()));

  connect (&myFilterModel, SIGNAL (rowsInserted (const QModelIndex&,int,int)), this, SLOT (refreshActionSensitivitySoon ()));
  connect (&myFilterModel, SIGNAL (rowsRemoved (const QModelIndex&,int,int)), this, SLOT (refreshActionSensitivitySoon ()));

  connect (ui.action_Quit, SIGNAL (triggered ()), QCoreApplication::instance (), SLOT (quit ()));

  // torrent view
  myFilterModel.setSourceModel (&myModel);
  connect (&myModel, SIGNAL (modelReset ()), this, SLOT (onModelReset ()));
  connect (&myModel, SIGNAL (rowsRemoved (const QModelIndex&,int,int)), this, SLOT (onModelReset ()));
  connect (&myModel, SIGNAL (rowsInserted (const QModelIndex&,int,int)), this, SLOT (onModelReset ()));
  connect (&myModel, SIGNAL (dataChanged (const QModelIndex&,const QModelIndex&)), this, SLOT (refreshTrayIconSoon ()));

  ui.listView->setModel (&myFilterModel);
  connect (ui.listView->selectionModel (), SIGNAL (selectionChanged (const QItemSelection&,const QItemSelection&)), this, SLOT (refreshActionSensitivitySoon ()));

  QActionGroup * actionGroup = new QActionGroup (this);
  actionGroup->addAction (ui.action_SortByActivity);
  actionGroup->addAction (ui.action_SortByAge);
  actionGroup->addAction (ui.action_SortByETA);
  actionGroup->addAction (ui.action_SortByName);
  actionGroup->addAction (ui.action_SortByProgress);
  actionGroup->addAction (ui.action_SortByQueue);
  actionGroup->addAction (ui.action_SortByRatio);
  actionGroup->addAction (ui.action_SortBySize);
  actionGroup->addAction (ui.action_SortByState);

  myAltSpeedAction = new QAction (tr ("Speed Limits"), this);
  myAltSpeedAction->setIcon (myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon);
  connect (myAltSpeedAction, SIGNAL (triggered ()), this, SLOT (toggleSpeedMode ()));

  QMenu * menu = new QMenu (this);
  menu->addAction (ui.action_OpenFile);
  menu->addAction (ui.action_AddURL);
  menu->addSeparator ();
  menu->addAction (ui.action_ShowMainWindow);
  menu->addAction (ui.action_ShowMessageLog);
  menu->addAction (ui.action_About);
  menu->addSeparator ();
  menu->addAction (ui.action_StartAll);
  menu->addAction (ui.action_PauseAll);
  menu->addAction (myAltSpeedAction);
  menu->addSeparator ();
  menu->addAction (ui.action_Quit);
  myTrayIcon.setContextMenu (menu);
  myTrayIcon.setIcon (QApplication::windowIcon ());

  connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
  connect (ui.action_ShowMainWindow, SIGNAL (triggered (bool)), this, SLOT (toggleWindows (bool)));
  connect (&myTrayIcon, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
           this, SLOT (trayActivated (QSystemTrayIcon::ActivationReason)));

  toggleWindows (!minimized);
  ui.action_TrayIcon->setChecked (minimized || prefs.getBool (Prefs::SHOW_TRAY_ICON));

  ui.verticalLayout->addWidget (createStatusBar ());
  ui.verticalLayout->insertWidget (0, myFilterBar = new FilterBar (myPrefs, myModel, myFilterModel));

  QList<int> initKeys;
  initKeys << Prefs :: MAIN_WINDOW_X
           << Prefs :: SHOW_TRAY_ICON
           << Prefs :: SORT_REVERSED
           << Prefs :: SORT_MODE
           << Prefs :: FILTERBAR
           << Prefs :: STATUSBAR
           << Prefs :: STATUSBAR_STATS
           << Prefs :: TOOLBAR
           << Prefs :: ALT_SPEED_LIMIT_ENABLED
           << Prefs :: COMPACT_VIEW
           << Prefs :: DSPEED
           << Prefs :: DSPEED_ENABLED
           << Prefs :: USPEED
           << Prefs :: USPEED_ENABLED
           << Prefs :: RATIO
           << Prefs :: RATIO_ENABLED;
  foreach (int key, initKeys)
    refreshPref (key);

  connect (&mySession, SIGNAL (sourceChanged ()), this, SLOT (onSessionSourceChanged ()));
  connect (&mySession, SIGNAL (statsUpdated ()), this, SLOT (refreshStatusBar ()));
  connect (&mySession, SIGNAL (dataReadProgress ()), this, SLOT (dataReadProgress ()));
  connect (&mySession, SIGNAL (dataSendProgress ()), this, SLOT (dataSendProgress ()));
  connect (&mySession, SIGNAL (httpAuthenticationRequired ()), this, SLOT (wrongAuthentication ()));

  if (mySession.isServer ())
    {
      myNetworkLabel->hide ();
    }
  else
    {
      connect (&myNetworkTimer, SIGNAL (timeout ()), this, SLOT (onNetworkTimer ()));
      myNetworkTimer.start (1000);
    }

  connect (&myRefreshTrayIconTimer, SIGNAL (timeout ()), this, SLOT (refreshTrayIcon ()));
  connect (&myRefreshActionSensitivityTimer, SIGNAL (timeout ()), this, SLOT (refreshActionSensitivity ()));


  refreshActionSensitivitySoon ();
  refreshTrayIconSoon ();
  refreshStatusBar ();
  refreshTitle ();
}

TrMainWindow :: ~TrMainWindow ()
{
}

/****
*****
****/

void
TrMainWindow :: onSessionSourceChanged ()
{
  myModel.clear ();
}

void
TrMainWindow :: onModelReset ()
{
  refreshTitle ();
  refreshActionSensitivitySoon ();
  refreshStatusBar ();
  refreshTrayIconSoon ();
}

/****
*****
****/

#define PREF_VARIANTS_KEY "pref-variants-list"

void
TrMainWindow :: onSetPrefs ()
{
  const QVariantList p = sender ()->property (PREF_VARIANTS_KEY).toList ();
  assert ( (p.size () % 2) == 0);
  for (int i=0, n=p.size (); i<n; i+=2)
    myPrefs.set (p[i].toInt (), p[i+1]);
}

void
TrMainWindow :: onSetPrefs (bool isChecked)
{
  if (isChecked)
    onSetPrefs ();
}

#define SHOW_KEY "show-mode"

QWidget *
TrMainWindow :: createStatusBar ()
{
  QMenu * m;
  QLabel * l;
  QHBoxLayout * h;
  QPushButton * p;
  QActionGroup * a;
  const int i = style ()->pixelMetric (QStyle::PM_SmallIconSize, 0, this);
  const QSize smallIconSize (i, i);

  QWidget * top = myStatusBar = new QWidget;
  h = new QHBoxLayout (top);
  h->setContentsMargins (HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL);
  h->setSpacing (HIG::PAD_SMALL);

    p = myOptionsButton = new TrIconPushButton (this);
    p->setIcon (QIcon (":/icons/utilities.png"));
    p->setIconSize (QPixmap (":/icons/utilities.png").size ());
    p->setFlat (true);
    p->setMenu (createOptionsMenu ());
    h->addWidget (p);

    p = myAltSpeedButton = new QPushButton (this);
    p->setIcon (myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon);
    p->setIconSize (QPixmap (":/icons/alt-limit-on.png").size ());
    p->setCheckable (true);
    p->setFixedWidth (p->height ());
    p->setFlat (true);
    h->addWidget (p);
    connect (p, SIGNAL (clicked ()), this, SLOT (toggleSpeedMode ()));

    l = myNetworkLabel = new QLabel;
    h->addWidget (l);

  h->addStretch (1);

    l = myDownloadSpeedLabel = new QLabel (this);
    const int minimumSpeedWidth = l->fontMetrics ().width (Formatter::uploadSpeedToString (Speed::fromKBps (999.99)));
    l->setMinimumWidth (minimumSpeedWidth);
    l->setAlignment (Qt::AlignRight|Qt::AlignVCenter);
    h->addWidget (l);

  h->addSpacing (HIG::PAD);

    l = myUploadSpeedLabel = new QLabel;
    l->setMinimumWidth (minimumSpeedWidth);
    l->setAlignment (Qt::AlignRight|Qt::AlignVCenter);
    h->addWidget (l);

  h->addSpacing (HIG::PAD);

    l = myStatsLabel = new QLabel (this);
    h->addWidget (l);
    a = new QActionGroup (this);
    a->addAction (ui.action_TotalRatio);
    a->addAction (ui.action_TotalTransfer);
    a->addAction (ui.action_SessionRatio);
    a->addAction (ui.action_SessionTransfer);
    m = new QMenu (this);
    m->addAction (ui.action_TotalRatio);
    m->addAction (ui.action_TotalTransfer);
    m->addAction (ui.action_SessionRatio);
    m->addAction (ui.action_SessionTransfer);
    connect (ui.action_TotalRatio, SIGNAL (triggered ()), this, SLOT (showTotalRatio ()));
    connect (ui.action_TotalTransfer, SIGNAL (triggered ()), this, SLOT (showTotalTransfer ()));
    connect (ui.action_SessionRatio, SIGNAL (triggered ()), this, SLOT (showSessionRatio ()));
    connect (ui.action_SessionTransfer, SIGNAL (triggered ()), this, SLOT (showSessionTransfer ()));
    p = myStatsModeButton = new TrIconPushButton (this);
    p->setIcon (QIcon (":/icons/ratio.png"));
    p->setIconSize (QPixmap (":/icons/ratio.png").size ());
    p->setFlat (true);
    p->setMenu (m);
    h->addWidget (p);

  return top;
}

QMenu *
TrMainWindow :: createOptionsMenu ()
{
  QMenu * menu;
  QMenu * sub;
  QAction * a;
  QActionGroup * g;

  QList<int> stockSpeeds;
  stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
  QList<double> stockRatios;
  stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;

  menu = new QMenu (this);
  sub = menu->addMenu (tr ("Limit Download Speed"));

    int currentVal = myPrefs.get<int> (Prefs::DSPEED);
    g = new QActionGroup (this);
    a = myDlimitOffAction = sub->addAction (tr ("Unlimited"));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED_ENABLED << false);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    a = myDlimitOnAction = sub->addAction (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (currentVal))));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    sub->addSeparator ();
    foreach (int i, stockSpeeds)
      {
        a = sub->addAction (Formatter::speedToString (Speed::fromKBps (i)));
        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true);
        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
      }

  sub = menu->addMenu (tr ("Limit Upload Speed"));

    currentVal = myPrefs.get<int> (Prefs::USPEED);
    g = new QActionGroup (this);
    a = myUlimitOffAction = sub->addAction (tr ("Unlimited"));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED_ENABLED << false);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    a = myUlimitOnAction = sub->addAction (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (currentVal))));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    sub->addSeparator ();
    foreach (int i, stockSpeeds)
      {
        a = sub->addAction (Formatter::speedToString (Speed::fromKBps (i)));
        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true);
        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
      }

  menu->addSeparator ();
  sub = menu->addMenu (tr ("Stop Seeding at Ratio"));

    double d = myPrefs.get<double> (Prefs::RATIO);
    g = new QActionGroup (this);
    a = myRatioOffAction = sub->addAction (tr ("Seed Forever"));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO_ENABLED << false);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    a = myRatioOnAction = sub->addAction (tr ("Stop at Ratio (%1)").arg (Formatter::ratioToString (d)));
    a->setCheckable (true);
    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true);
    g->addAction (a);
    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
    sub->addSeparator ();
    foreach (double i, stockRatios)
      {
        a = sub->addAction (Formatter::ratioToString (i));
        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true);
        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
      }

  return menu;
}

/****
*****
****/

void
TrMainWindow :: setSortPref (int i)
{
  myPrefs.set (Prefs::SORT_MODE, SortMode (i));
}
void TrMainWindow :: onSortByActivityToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_ACTIVITY); }
void TrMainWindow :: onSortByAgeToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_AGE); }
void TrMainWindow :: onSortByETAToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_ETA); }
void TrMainWindow :: onSortByNameToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_NAME); }
void TrMainWindow :: onSortByProgressToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_PROGRESS); }
void TrMainWindow :: onSortByQueueToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_QUEUE); }
void TrMainWindow :: onSortByRatioToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_RATIO); }
void TrMainWindow :: onSortBySizeToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_SIZE); }
void TrMainWindow :: onSortByStateToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_STATE); }

void
TrMainWindow :: setSortAscendingPref (bool b)
{
  myPrefs.set (Prefs::SORT_REVERSED, b);
}

/****
*****
****/

void
TrMainWindow :: showEvent (QShowEvent * event)
{
  Q_UNUSED (event);

  ui.action_ShowMainWindow->setChecked (true);
}

/****
*****
****/

void
TrMainWindow :: hideEvent (QHideEvent * event)
{
  Q_UNUSED (event);

  if (!isVisible ())
    ui.action_ShowMainWindow->setChecked (false);
}

/****
*****
****/

void
TrMainWindow :: onPrefsDestroyed ()
{
  myPrefsDialog = 0;
}

void
TrMainWindow :: openPreferences ()
{
  if (myPrefsDialog == 0)
    {
      myPrefsDialog = new PrefsDialog (mySession, myPrefs, this);
      connect (myPrefsDialog, SIGNAL (destroyed (QObject*)), this, SLOT (onPrefsDestroyed ()));
    }

  myPrefsDialog->show ();
}

void
TrMainWindow :: onDetailsDestroyed ()
{
  myDetailsDialog = 0;
}

void
TrMainWindow :: openProperties ()
{
  if (myDetailsDialog == 0)
    {
      myDetailsDialog = new Details (mySession, myPrefs, myModel, this);
      connect (myDetailsDialog, SIGNAL (destroyed (QObject*)), this, SLOT (onDetailsDestroyed ()));
    }

  myDetailsDialog->setIds (getSelectedTorrents ());
  myDetailsDialog->show ();
}

void
TrMainWindow :: setLocation ()
{
  QDialog * d = new RelocateDialog (mySession, myModel, getSelectedTorrents (), this);
  d->show ();
}

// Open Folder & select torrent's file or top folder
#undef HAVE_OPEN_SELECT
#if defined (Q_OS_WIN)
# define HAVE_OPEN_SELECT
static
void openSelect (const QString& path)
{
  const QString explorer = "explorer";
  QString param;
  if (!QFileInfo (path).isDir ())
    param = QLatin1String ("/select,");
  param += QDir::toNativeSeparators (path);
  QProcess::startDetached (explorer, QStringList (param));
}
#elif defined (Q_OS_MAC)
# define HAVE_OPEN_SELECT
static
void openSelect (const QString& path)
{
  QStringList scriptArgs;
  scriptArgs << QLatin1String ("-e")
             << QString::fromLatin1 ("tell application \"Finder\" to reveal POSIX file \"%1\"").arg (path);
  QProcess::execute (QLatin1String ("/usr/bin/osascript"), scriptArgs);

  scriptArgs.clear ();
  scriptArgs << QLatin1String ("-e")
             << QLatin1String ("tell application \"Finder\" to activate");
  QProcess::execute ("/usr/bin/osascript", scriptArgs);
}
#endif

void
TrMainWindow :: openFolder ()
{
  const int torrentId (*getSelectedTorrents ().begin ());
  const Torrent * tor (myModel.getTorrentFromId (torrentId));
  QString path (tor->getPath ());
  const FileList files = tor->files ();
  const QString firstfile = files.at (0).filename;
  int slashIndex = firstfile.indexOf ('/');
  if (slashIndex > -1)
    {
      path = path + "/" + firstfile.left (slashIndex);
    }
#ifdef HAVE_OPEN_SELECT
  else
    {
      openSelect (path + "/" + firstfile);
      return;
    }
#endif
  QDesktopServices :: openUrl (QUrl::fromLocalFile (path));
}

void
TrMainWindow :: copyMagnetLinkToClipboard ()
{
  const int id (*getSelectedTorrents ().begin ());
  mySession.copyMagnetLinkToClipboard (id);
}

void
TrMainWindow :: openDonate ()
{
  QDesktopServices :: openUrl (QUrl ("http://www.transmissionbt.com/donate.php"));
}

void
TrMainWindow :: openHelp ()
{
  const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
  int major, minor;
  sscanf (SHORT_VERSION_STRING, "%d.%d", &major, &minor);
  char url[128];
  tr_snprintf (url, sizeof (url), fmt, major, minor/10);
  QDesktopServices :: openUrl (QUrl (url));
}

void
TrMainWindow :: refreshTitle ()
{
  QString title ("Transmission");
  const QUrl url (mySession.getRemoteUrl ());
  if (!url.isEmpty ())
    title += tr (" - %1:%2").arg (url.host ()).arg (url.port ());
  setWindowTitle (title);
}

void
TrMainWindow :: refreshTrayIconSoon ()
{
  if (!myRefreshTrayIconTimer.isActive ())
    {
      myRefreshTrayIconTimer.setSingleShot (true);
      myRefreshTrayIconTimer.start (100);
    }
}
void
TrMainWindow :: refreshTrayIcon ()
{
  Speed upSpeed, downSpeed;
  size_t upCount, downCount;
  QString tip;

  myModel.getTransferSpeed (upSpeed, upCount, downSpeed, downCount);

  if (!upCount && !downCount)
    {
      tip = tr ("Idle");
    }
  else if (downCount)
    {
      tip  = tr( "%1   %2" ).arg(Formatter::downloadSpeedToString(downSpeed))
                            .arg(Formatter::uploadSpeedToString(upSpeed));
    }
  else if (upCount)
    {
      tip = Formatter::uploadSpeedToString(upSpeed);
    }

  myTrayIcon.setToolTip (tip);
}

void
TrMainWindow :: refreshStatusBar ()
{
  Speed upSpeed, downSpeed;
  size_t upCount, downCount;
  myModel.getTransferSpeed (upSpeed, upCount, downSpeed, downCount);

  myUploadSpeedLabel->setText (Formatter::uploadSpeedToString(upSpeed));
  myUploadSpeedLabel->setVisible (downCount || upCount);
  myDownloadSpeedLabel->setText (Formatter::downloadSpeedToString(downSpeed));
  myDownloadSpeedLabel->setVisible (downCount);

  myNetworkLabel->setVisible (!mySession.isServer ());

  const QString mode (myPrefs.getString (Prefs::STATUSBAR_STATS));
  QString str;

  if (mode == "session-ratio")
    {
      str = tr ("Ratio: %1").arg (Formatter:: ratioToString (mySession.getStats ().ratio));
    }
  else if (mode == "session-transfer")
    {
      const tr_session_stats& stats (mySession.getStats ());
      str = tr ("Down: %1, Up: %2").arg (Formatter:: sizeToString (stats.downloadedBytes))
                                      .arg (Formatter:: sizeToString (stats.uploadedBytes));
    }
  else if (mode == "total-transfer")
    {
      const tr_session_stats& stats (mySession.getCumulativeStats ());
      str = tr ("Down: %1, Up: %2").arg (Formatter:: sizeToString (stats.downloadedBytes))
                                   .arg (Formatter:: sizeToString (stats.uploadedBytes));
    }
  else // default is "total-ratio"
    {
      str = tr ("Ratio: %1").arg (Formatter:: ratioToString (mySession.getCumulativeStats ().ratio));
    }

  myStatsLabel->setText (str);
}



void
TrMainWindow :: refreshActionSensitivitySoon ()
{
  if (!myRefreshActionSensitivityTimer.isActive ())
    {
      myRefreshActionSensitivityTimer.setSingleShot (true);
      myRefreshActionSensitivityTimer.start (100);
    }
}
void
TrMainWindow :: refreshActionSensitivity ()
{
  int selected (0);
  int paused (0);
  int queued (0);
  int selectedAndPaused (0);
  int selectedAndQueued (0);
  int canAnnounce (0);
  const QAbstractItemModel * model (ui.listView->model ());
  const QItemSelectionModel * selectionModel (ui.listView->selectionModel ());
  const int rowCount (model->rowCount ());

  // count how many torrents are selected, paused, etc
  for (int row=0; row<rowCount; ++row)
    {
      const QModelIndex modelIndex (model->index (row, 0));
      assert (model == modelIndex.model ());
      const Torrent * tor (model->data (modelIndex, TorrentModel::TorrentRole).value<const Torrent*> ());
      if (tor)
        {
          const bool isSelected (selectionModel->isSelected (modelIndex));
          const bool isPaused (tor->isPaused ());
          const bool isQueued (tor->isQueued ());
          if (isSelected) ++selected;
          if (isQueued) ++queued;
          if (isPaused) ++ paused;
          if (isSelected && isPaused) ++selectedAndPaused;
          if (isSelected && isQueued) ++selectedAndQueued;
          if (tor->canManualAnnounce ()) ++canAnnounce;
        }
    }

  const bool haveSelection (selected > 0);
  ui.action_Verify->setEnabled (haveSelection);
  ui.action_Remove->setEnabled (haveSelection);
  ui.action_Delete->setEnabled (haveSelection);
  ui.action_Properties->setEnabled (haveSelection);
  ui.action_DeselectAll->setEnabled (haveSelection);
  ui.action_SetLocation->setEnabled (haveSelection);

  const bool oneSelection (selected == 1);
  ui.action_OpenFolder->setEnabled (oneSelection && mySession.isLocal ());
  ui.action_CopyMagnetToClipboard->setEnabled (oneSelection);

  ui.action_SelectAll->setEnabled (selected < rowCount);
  ui.action_StartAll->setEnabled (paused > 0);
  ui.action_PauseAll->setEnabled (paused < rowCount);
  ui.action_Start->setEnabled (selectedAndPaused > 0);
  ui.action_StartNow->setEnabled (selectedAndPaused + selectedAndQueued > 0);
  ui.action_Pause->setEnabled (selectedAndPaused < selected);
  ui.action_Announce->setEnabled (selected > 0 && (canAnnounce == selected));

  ui.action_QueueMoveTop->setEnabled (haveSelection);
  ui.action_QueueMoveUp->setEnabled (haveSelection);
  ui.action_QueueMoveDown->setEnabled (haveSelection);
  ui.action_QueueMoveBottom->setEnabled (haveSelection);

  if (myDetailsDialog)
    myDetailsDialog->setIds (getSelectedTorrents ());
}

/**
***
**/

void
TrMainWindow :: clearSelection ()
{
  ui.action_DeselectAll->trigger ();
}

QSet<int>
TrMainWindow :: getSelectedTorrents () const
{
  QSet<int> ids;

  foreach (QModelIndex index, ui.listView->selectionModel ()->selectedRows ())
    {
      const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*> ());
      ids.insert (tor->id ());
    }

  return ids;
}

void
TrMainWindow :: startSelected ()
{
  mySession.startTorrents (getSelectedTorrents ());
}
void
TrMainWindow :: startSelectedNow ()
{
  mySession.startTorrentsNow (getSelectedTorrents ());
}
void
TrMainWindow :: pauseSelected ()
{
  mySession.pauseTorrents (getSelectedTorrents ());
}
void
TrMainWindow :: queueMoveTop ()
{
  mySession.queueMoveTop (getSelectedTorrents ());
}
void
TrMainWindow :: queueMoveUp ()
{
  mySession.queueMoveUp (getSelectedTorrents ());
}
void
TrMainWindow :: queueMoveDown ()
{
  mySession.queueMoveDown (getSelectedTorrents ());
}
void
TrMainWindow :: queueMoveBottom ()
{
  mySession.queueMoveBottom (getSelectedTorrents ());
}
void
TrMainWindow :: startAll ()
{
  mySession.startTorrents ();
}
void
TrMainWindow :: pauseAll ()
{
  mySession.pauseTorrents ();
}
void
TrMainWindow :: removeSelected ()
{
  removeTorrents (false);
}
void
TrMainWindow :: deleteSelected ()
{
  removeTorrents (true);
}
void
TrMainWindow :: verifySelected ()
{
  mySession.verifyTorrents (getSelectedTorrents ());
}
void
TrMainWindow :: reannounceSelected ()
{
  mySession.reannounceTorrents (getSelectedTorrents ());
}

/**
***
**/

void TrMainWindow :: showTotalRatio () { myPrefs.set (Prefs::STATUSBAR_STATS, "total-ratio"); }
void TrMainWindow :: showTotalTransfer () { myPrefs.set (Prefs::STATUSBAR_STATS, "total-transfer"); }
void TrMainWindow :: showSessionRatio () { myPrefs.set (Prefs::STATUSBAR_STATS, "session-ratio"); }
void TrMainWindow :: showSessionTransfer () { myPrefs.set (Prefs::STATUSBAR_STATS, "session-transfer"); }

/**
***
**/

void
TrMainWindow :: setCompactView (bool visible)
{
  myPrefs.set (Prefs :: COMPACT_VIEW, visible);
}
void
TrMainWindow :: toggleSpeedMode ()
{
  myPrefs.toggleBool (Prefs :: ALT_SPEED_LIMIT_ENABLED);
  const bool mode = myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED);
  myAltSpeedAction->setIcon (mode ? mySpeedModeOnIcon : mySpeedModeOffIcon);
}
void
TrMainWindow :: setToolbarVisible (bool visible)
{
  myPrefs.set (Prefs::TOOLBAR, visible);
}
void
TrMainWindow :: setFilterbarVisible (bool visible)
{
  myPrefs.set (Prefs::FILTERBAR, visible);
}
void
TrMainWindow :: setStatusbarVisible (bool visible)
{
  myPrefs.set (Prefs::STATUSBAR, visible);
}

/**
***
**/

void
TrMainWindow :: toggleWindows (bool doShow)
{
  if (!doShow)
    {
      hide ();
    }
  else
    {
      if (!isVisible ()) show ();
      if (isMinimized ()) showNormal ();
      //activateWindow ();
      raise ();
      QApplication::setActiveWindow (this);
    }
}

void
TrMainWindow :: trayActivated (QSystemTrayIcon::ActivationReason reason)
{
  if (reason == QSystemTrayIcon::Trigger)
    {
      if (isMinimized ())
        toggleWindows (true);
      else
        toggleWindows (!isVisible ());
    }
}


void
TrMainWindow :: refreshPref (int key)
{
  bool b;
  int i;
  QString str;

  switch (key)
    {
      case Prefs::STATUSBAR_STATS:
        str = myPrefs.getString (key);
        ui.action_TotalRatio->setChecked (str == "total-ratio");
        ui.action_TotalTransfer->setChecked (str == "total-transfer");
        ui.action_SessionRatio->setChecked (str == "session-ratio");
        ui.action_SessionTransfer->setChecked (str == "session-transfer");
        refreshStatusBar ();
        break;

      case Prefs::SORT_REVERSED:
        ui.action_ReverseSortOrder->setChecked (myPrefs.getBool (key));
        break;

      case Prefs::SORT_MODE:
        i = myPrefs.get<SortMode> (key).mode ();
        ui.action_SortByActivity->setChecked (i == SortMode::SORT_BY_ACTIVITY);
        ui.action_SortByAge->setChecked (i == SortMode::SORT_BY_AGE);
        ui.action_SortByETA->setChecked (i == SortMode::SORT_BY_ETA);
        ui.action_SortByName->setChecked (i == SortMode::SORT_BY_NAME);
        ui.action_SortByProgress->setChecked (i == SortMode::SORT_BY_PROGRESS);
        ui.action_SortByQueue->setChecked (i == SortMode::SORT_BY_QUEUE);
        ui.action_SortByRatio->setChecked (i == SortMode::SORT_BY_RATIO);
        ui.action_SortBySize->setChecked (i == SortMode::SORT_BY_SIZE);
        ui.action_SortByState->setChecked (i == SortMode::SORT_BY_STATE);
        break;

      case Prefs::DSPEED_ENABLED:
        (myPrefs.get<bool> (key) ? myDlimitOnAction : myDlimitOffAction)->setChecked (true);
        break;

      case Prefs::DSPEED:
        myDlimitOnAction->setText (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (myPrefs.get<int> (key)))));
        break;

      case Prefs::USPEED_ENABLED:
        (myPrefs.get<bool> (key) ? myUlimitOnAction : myUlimitOffAction)->setChecked (true);
        break;

      case Prefs::USPEED:
        myUlimitOnAction->setText (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (myPrefs.get<int> (key)))));
        break;

      case Prefs::RATIO_ENABLED:
        (myPrefs.get<bool> (key) ? myRatioOnAction : myRatioOffAction)->setChecked (true);
        break;

      case Prefs::RATIO:
        myRatioOnAction->setText (tr ("Stop at Ratio (%1)").arg (Formatter::ratioToString (myPrefs.get<double> (key))));
        break;

      case Prefs::FILTERBAR:
        b = myPrefs.getBool (key);
        myFilterBar->setVisible (b);
        ui.action_Filterbar->setChecked (b);
        break;

      case Prefs::STATUSBAR:
        b = myPrefs.getBool (key);
        myStatusBar->setVisible (b);
        ui.action_Statusbar->setChecked (b);
        break;

      case Prefs::TOOLBAR:
        b = myPrefs.getBool (key);
        ui.toolBar->setVisible (b);
        ui.action_Toolbar->setChecked (b);
        break;

      case Prefs::SHOW_TRAY_ICON:
        b = myPrefs.getBool (key);
        ui.action_TrayIcon->setChecked (b);
        myTrayIcon.setVisible (b);
        dynamic_cast<MyApp*> (QCoreApplication::instance ())->setQuitOnLastWindowClosed (!b);
        refreshTrayIconSoon ();
        break;

      case Prefs::COMPACT_VIEW: {
            QItemSelectionModel * selectionModel (ui.listView->selectionModel ());
            const QItemSelection selection (selectionModel->selection ());
            const QModelIndex currentIndex (selectionModel->currentIndex ());
            b = myPrefs.getBool (key);
            ui.action_CompactView->setChecked (b);
            ui.listView->setItemDelegate (b ? myTorrentDelegateMin : myTorrentDelegate);
            selectionModel->clear ();
            ui.listView->reset (); // force the rows to resize
            selectionModel->select (selection, QItemSelectionModel::Select);
            selectionModel->setCurrentIndex (currentIndex, QItemSelectionModel::NoUpdate);
            break;
        }

      case Prefs::MAIN_WINDOW_X:
      case Prefs::MAIN_WINDOW_Y:
      case Prefs::MAIN_WINDOW_WIDTH:
      case Prefs::MAIN_WINDOW_HEIGHT:
        setGeometry (myPrefs.getInt (Prefs::MAIN_WINDOW_X),
                     myPrefs.getInt (Prefs::MAIN_WINDOW_Y),
                     myPrefs.getInt (Prefs::MAIN_WINDOW_WIDTH),
                     myPrefs.getInt (Prefs::MAIN_WINDOW_HEIGHT));
        break;

      case Prefs :: ALT_SPEED_LIMIT_ENABLED:
      case Prefs :: ALT_SPEED_LIMIT_UP:
      case Prefs :: ALT_SPEED_LIMIT_DOWN:
        {
          b = myPrefs.getBool (Prefs :: ALT_SPEED_LIMIT_ENABLED);
          myAltSpeedButton->setChecked (b);
          myAltSpeedButton->setIcon (b ? mySpeedModeOnIcon : mySpeedModeOffIcon);
          const QString fmt = b ? tr ("Click to disable Temporary Speed Limits\n (%1 down, %2 up)")
                                : tr ("Click to enable Temporary Speed Limits\n (%1 down, %2 up)");
          const Speed d = Speed::fromKBps (myPrefs.getInt (Prefs::ALT_SPEED_LIMIT_DOWN));
          const Speed u = Speed::fromKBps (myPrefs.getInt (Prefs::ALT_SPEED_LIMIT_UP));
          myAltSpeedButton->setToolTip (fmt.arg (Formatter::speedToString (d))
                                           .arg (Formatter::speedToString (u)));
          break;
        }

      default:
        break;
    }
}

/***
****
***/

void
TrMainWindow :: newTorrent ()
{
  MakeDialog * dialog = new MakeDialog (mySession, this);
  dialog->show ();
}

void
TrMainWindow :: openTorrent ()
{
  QFileDialog * myFileDialog;
  myFileDialog = new QFileDialog (this,
                                  tr ("Open Torrent"),
                                  myPrefs.getString (Prefs::OPEN_DIALOG_FOLDER),
                                  tr ("Torrent Files (*.torrent);;All Files (*.*)"));
  myFileDialog->setFileMode (QFileDialog::ExistingFiles);
  myFileDialog->setAttribute (Qt::WA_DeleteOnClose);

  QCheckBox * button = new QCheckBox (tr ("Show &options dialog"));
  button->setChecked (myPrefs.getBool (Prefs::OPTIONS_PROMPT));
  QGridLayout * layout = dynamic_cast<QGridLayout*> (myFileDialog->layout ());
  layout->addWidget (button, layout->rowCount (), 0, 1, -1, Qt::AlignLeft);
  myFileDialogOptionsCheck = button;

  connect (myFileDialog, SIGNAL (filesSelected (const QStringList&)),
           this, SLOT (addTorrents (const QStringList&)));

  myFileDialog->show ();
}

void
TrMainWindow :: openURL ()
{
  QString str = QApplication::clipboard ()->text (QClipboard::Selection);

  if (!AddData::isSupported (str))
    str = QApplication::clipboard ()->text (QClipboard::Clipboard);

  if (!AddData::isSupported (str))
    str.clear ();

  addTorrent (str);
}

void
TrMainWindow :: addTorrents (const QStringList& filenames)
{
  foreach (const QString& filename, filenames)
    addTorrent (filename);
}

void
TrMainWindow :: addTorrent (const AddData& addMe)
{
  bool show_options_dialog;

  if (myFileDialogOptionsCheck)
    show_options_dialog = myFileDialogOptionsCheck->isChecked ();
  else
    show_options_dialog = myPrefs.getBool (Prefs::OPTIONS_PROMPT);

  if (show_options_dialog)
    {
      Options * o = new Options (mySession, myPrefs, addMe, this);
      o->show ();
      QApplication :: alert (o);
    }
  else
    {
      mySession.addTorrent (addMe);
      QApplication :: alert (this);
    }
}

void
TrMainWindow :: removeTorrents (const bool deleteFiles)
{
  QSet<int> ids;
  QMessageBox msgBox (this);
  QString primary_text, secondary_text;
  int incomplete = 0;
  int connected  = 0;
  int count;

  foreach (QModelIndex index, ui.listView->selectionModel ()->selectedRows ())
    {
      const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*> ());
      ids.insert (tor->id ());

      if (tor->connectedPeers ())
        ++connected;

      if (!tor->isDone ())
        ++incomplete;
    }

  if (ids.isEmpty ())
    return;

  count = ids.size ();

  if (!deleteFiles)
    {
      primary_text = (count == 1)
        ? tr ("Remove torrent?")
        : tr ("Remove %1 torrents?").arg (count);
    }
  else
    {
      primary_text = (count == 1)
        ? tr ("Delete this torrent's downloaded files?")
        : tr ("Delete these %1 torrents' downloaded files?").arg (count);
    }

  if (!incomplete && !connected)
    {
      secondary_text = (count == 1)
        ? tr ("Once removed, continuing the transfer will require the torrent file or magnet link.")
        : tr ("Once removed, continuing the transfers will require the torrent files or magnet links.");
    }
  else if (count == incomplete)
    {
      secondary_text = (count == 1)
        ? tr ("This torrent has not finished downloading.")
        : tr ("These torrents have not finished downloading.");
    }
  else if (count == connected)
    {
      secondary_text = (count == 1)
        ? tr ("This torrent is connected to peers.")
        : tr ("These torrents are connected to peers.");
    }
  else
    {
      if (connected)
        {
          secondary_text = (connected == 1)
            ? tr ("One of these torrents is connected to peers.")
            : tr ("Some of these torrents are connected to peers.");
        }

      if (connected && incomplete)
        {
          secondary_text += "\n";
        }

      if (incomplete)
        {
          secondary_text += (incomplete == 1)
            ? tr ("One of these torrents has not finished downloading.")
            : tr ("Some of these torrents have not finished downloading.");
        }
    }

  msgBox.setWindowTitle (QString (" "));
  msgBox.setText (QString ("<big><b>%1</big></b>").arg (primary_text));
  msgBox.setInformativeText (secondary_text);
  msgBox.setStandardButtons (QMessageBox::Ok | QMessageBox::Cancel);
  msgBox.setDefaultButton (QMessageBox::Cancel);
  msgBox.setIcon (QMessageBox::Question);
  /* hack needed to keep the dialog from being too narrow */
  QGridLayout* layout = (QGridLayout*)msgBox.layout ();
  QSpacerItem* spacer = new QSpacerItem (450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
  layout->addItem (spacer, layout->rowCount (), 0, 1, layout->columnCount ());

  if (msgBox.exec () == QMessageBox::Ok)
    {
      ui.listView->selectionModel ()->clear ();
      mySession.removeTorrents (ids, deleteFiles);
    }
}

/***
****
***/

void
TrMainWindow :: updateNetworkIcon ()
{
  const time_t now = time (NULL);
  const int period = 3;
  const time_t secondsSinceLastSend = now - myLastSendTime;
  const time_t secondsSinceLastRead = now - myLastReadTime;
  const bool isSending = secondsSinceLastSend <= period;
  const bool isReading = secondsSinceLastRead <= period;
  const char * key;

  if (isSending && isReading)
    key = "network-transmit-receive";
  else if (isSending)
    key = "network-transmit";
  else if (isReading)
    key = "network-receive";
  else
    key = "network-idle";
  const QIcon icon = getStockIcon (key, QStyle::SP_DriveNetIcon);
  const QPixmap pixmap = icon.pixmap (16, 16);

  QString tip;
  const QString url = mySession.getRemoteUrl ().host ();
  if (!myLastReadTime)
    tip = tr ("%1 has not responded yet").arg (url);
  else if (secondsSinceLastRead < 60)
    tip = tr ("%1 is responding").arg (url);
  else if (secondsSinceLastRead < (60*10))
    tip = tr ("%1 last responded %2 ago").arg (url).arg (Formatter::timeToString (secondsSinceLastRead));
  else
    tip = tr ("%1 is not responding").arg (url);

  myNetworkLabel->setPixmap (pixmap);
  myNetworkLabel->setToolTip (tip);
}

void
TrMainWindow :: onNetworkTimer ()
{
  updateNetworkIcon ();
}

void
TrMainWindow :: dataReadProgress ()
{
  myLastReadTime = time (NULL);
  updateNetworkIcon ();
}

void
TrMainWindow :: dataSendProgress ()
{
  myLastSendTime = time (NULL);
  updateNetworkIcon ();
}

void
TrMainWindow :: wrongAuthentication ()
{
  mySession.stop ();
  mySessionDialog->show ();
}

/***
****
***/

void
TrMainWindow :: dragEnterEvent (QDragEnterEvent * event)
{
  const QMimeData * mime = event->mimeData ();

  if (mime->hasFormat ("application/x-bittorrent")
        || mime->hasUrls()
        || mime->text ().trimmed ().endsWith (".torrent", Qt::CaseInsensitive)
        || mime->text ().startsWith ("magnet:", Qt::CaseInsensitive))
    event->acceptProposedAction ();
}

void
TrMainWindow :: dropEvent (QDropEvent * event)
{
  QStringList list;

  if (event->mimeData()->hasText())
    {
      list = event->mimeData()->text().trimmed().split('\n');
    }
  else if (event->mimeData()->hasUrls())
    {
      foreach (QUrl url, event->mimeData()->urls())
        list.append(url.toLocalFile());
    }

  foreach (QString entry, list)
    {
      QString key = entry.trimmed();

      if (!key.isEmpty())
        {
          const QUrl url (key);

          if (url.scheme () == "file")
            key = QUrl::fromPercentEncoding (url.path().toUtf8());

          dynamic_cast<MyApp*> (QApplication::instance ())->addTorrent (key);
        }
    }
}

/***
****
***/

void
TrMainWindow :: contextMenuEvent (QContextMenuEvent * event)
{
  QMenu * menu = new QMenu (this);

  menu->addAction (ui.action_Properties);
  menu->addAction (ui.action_OpenFolder);

  QAction * sep = new QAction (menu);
  sep->setSeparator (true);
  menu->addAction (sep);
  menu->addAction (ui.action_Start);
  menu->addAction (ui.action_StartNow);
  menu->addAction (ui.action_Announce);
  QMenu * queueMenu = menu->addMenu (tr ("Queue"));
    queueMenu->addAction (ui.action_QueueMoveTop);
    queueMenu->addAction (ui.action_QueueMoveUp);
    queueMenu->addAction (ui.action_QueueMoveDown);
    queueMenu->addAction (ui.action_QueueMoveBottom);
  menu->addAction (ui.action_Pause);

  sep = new QAction (menu);
  sep->setSeparator (true);
  menu->addAction (sep);
  menu->addAction (ui.action_Verify);
  menu->addAction (ui.action_SetLocation);
  menu->addAction (ui.action_CopyMagnetToClipboard);

  sep = new QAction (menu);
  sep->setSeparator (true);
  menu->addAction (sep);
  menu->addAction (ui.action_Remove);
  menu->addAction (ui.action_Delete);

  menu->popup (event->globalPos ());
}
