/*
 * This file Copyright (C) 2009-2014 Mnemosyne LLC
 *
 * It may be used under the GNU Public License v2 or v3 licenses,
 * or any future license endorsed by Mnemosyne LLC.
 *
 * $Id: prefs-dialog.cc 14225 2014-01-19 01:09:44Z jordan $
 */

#include <cassert>
#include <climits> /* INT_MAX */
#include <iostream>

#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFileIconProvider>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <QStyle>
#include <QTabWidget>
#include <QTime>
#include <QTimeEdit>
#include <QTimer>
#include <QVBoxLayout>

#include "freespace-label.h"
#include "formatter.h"
#include "hig.h"
#include "prefs.h"
#include "prefs-dialog.h"
#include "session.h"
#include "utils.h"

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

namespace
{
  const char * PREF_KEY ("pref-key");
};

void
PrefsDialog :: checkBoxToggled (bool checked)
{
  const int key (sender ()->property (PREF_KEY).toInt ());
  setPref (key, checked);
}

QCheckBox *
PrefsDialog :: checkBoxNew (const QString& text, int key)
{
  QCheckBox * box = new QCheckBox (text);
  box->setChecked (myPrefs.getBool (key));
  box->setProperty (PREF_KEY, key);
  connect (box, SIGNAL(toggled(bool)), this, SLOT(checkBoxToggled(bool)));
  myWidgets.insert (key, box);
  return box;
}

void
PrefsDialog :: enableBuddyWhenChecked (QCheckBox * box, QWidget * buddy)
{
  connect (box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)));
  buddy->setEnabled (box->isChecked ());
}

void
PrefsDialog :: spinBoxEditingFinished()
{
  const QObject * spin = sender();
  const int key = spin->property (PREF_KEY).toInt ();
  const QDoubleSpinBox * d = qobject_cast<const QDoubleSpinBox*> (spin);

  if (d)
    setPref (key, d->value ());
  else
    setPref (key, qobject_cast<const QSpinBox*>(spin)->value ());
}

QSpinBox *
PrefsDialog :: spinBoxNew (int key, int low, int high, int step)
{
  QSpinBox * spin = new QSpinBox ();
  spin->setRange (low, high);
  spin->setSingleStep (step);
  spin->setValue (myPrefs.getInt (key));
  spin->setProperty (PREF_KEY, key);
  connect (spin, SIGNAL(editingFinished()), this, SLOT(spinBoxEditingFinished()));
  myWidgets.insert (key, spin);
  return spin;
}

QDoubleSpinBox *
PrefsDialog :: doubleSpinBoxNew (int key, double low, double high, double step, int decimals)
{
  QDoubleSpinBox * spin = new QDoubleSpinBox ();
  spin->setRange (low, high);
  spin->setSingleStep (step);
  spin->setDecimals (decimals);
  spin->setValue (myPrefs.getDouble (key));
  spin->setProperty (PREF_KEY, key);
  connect (spin, SIGNAL(editingFinished()), this, SLOT(spinBoxEditingFinished()));
  myWidgets.insert (key, spin);
  return spin;
}

void
PrefsDialog :: timeEditingFinished ()
{
  QTimeEdit * e = qobject_cast<QTimeEdit*>(sender());
  if (e)
    {
      const int key (e->property (PREF_KEY).toInt ());
      const QTime time (e->time ());
      const int seconds (QTime().secsTo (time));
      setPref (key, seconds / 60);
    }
}

QTimeEdit*
PrefsDialog :: timeEditNew (int key)
{
  const int minutes (myPrefs.getInt (key));
  QTimeEdit * e = new QTimeEdit ();
  e->setDisplayFormat (QString::fromUtf8 ("hh:mm"));
  e->setProperty (PREF_KEY, key);
  e->setTime (QTime().addSecs (minutes * 60));
  myWidgets.insert (key, e);
  connect (e, SIGNAL(editingFinished()), this, SLOT(timeEditingFinished()));
  return e;
}

void
PrefsDialog :: lineEditingFinished ()
{
  QLineEdit * e = qobject_cast<QLineEdit*>(sender());
  if (e && e->isModified ())
    {
      const int key (e->property (PREF_KEY).toInt ());
      const QString text (e->text());
      setPref (key, text);
    }
}

QLineEdit*
PrefsDialog :: lineEditNew (int key, int echoMode)
{
  QLineEdit * e = new QLineEdit (myPrefs.getString (key));
  e->setProperty (PREF_KEY, key);
  e->setEchoMode (QLineEdit::EchoMode (echoMode));
  myWidgets.insert (key, e);
  connect (e, SIGNAL(editingFinished()), this, SLOT(lineEditingFinished()));
  return e;
}

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

QWidget *
PrefsDialog :: createRemoteTab (Session& session)
{
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Remote Control"));
  QWidget * w;
  QHBoxLayout * h = new QHBoxLayout ();
  QPushButton * b = new QPushButton (tr ("&Open web client"));
  connect (b, SIGNAL(clicked()), &session, SLOT(launchWebInterface()));
  h->addWidget (b, 0, Qt::AlignRight);
  QWidget * l = checkBoxNew (tr ("Allow &remote access"), Prefs::RPC_ENABLED);
  myUnsupportedWhenRemote << l;
  hig->addRow (l, h, 0);
  l = hig->addRow (tr ("HTTP &port:"), w = spinBoxNew (Prefs::RPC_PORT, 0, 65535, 1));
  myWebWidgets << l << w;
  hig->addWideControl (w = checkBoxNew (tr ("Use &authentication"), Prefs::RPC_AUTH_REQUIRED));
  myWebWidgets << w;
  l = hig->addRow (tr ("&Username:"), w = lineEditNew (Prefs::RPC_USERNAME));
  myWebAuthWidgets << l << w;
  l = hig->addRow (tr ("Pass&word:"), w = lineEditNew (Prefs::RPC_PASSWORD, QLineEdit::Password));
  myWebAuthWidgets << l << w;
  hig->addWideControl (w = checkBoxNew (tr ("Only allow these IP a&ddresses:"), Prefs::RPC_WHITELIST_ENABLED));
  myWebWidgets << w;
  l = hig->addRow (tr ("Addresses:"), w = lineEditNew (Prefs::RPC_WHITELIST));
  myWebWhitelistWidgets << l << w;
  myUnsupportedWhenRemote << myWebWidgets << myWebAuthWidgets << myWebWhitelistWidgets;
  hig->finish ();
  return hig;
}

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

void
PrefsDialog :: altSpeedDaysEdited (int i)
{
  const int value = qobject_cast<QComboBox*>(sender())->itemData(i).toInt();
  setPref (Prefs::ALT_SPEED_LIMIT_TIME_DAY, value);
}


QWidget *
PrefsDialog :: createSpeedTab ()
{
  QWidget *l, *r;
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Speed Limits"));
  const QString speed_K_str = Formatter::unitStr (Formatter::SPEED, Formatter::KB);

    l = checkBoxNew (tr ("&Upload (%1):").arg (speed_K_str), Prefs::USPEED_ENABLED);
    r = spinBoxNew (Prefs::USPEED, 0, INT_MAX, 5);
    hig->addRow (l, r);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), r);

    l = checkBoxNew (tr ("&Download (%1):").arg (speed_K_str), Prefs::DSPEED_ENABLED);
    r = spinBoxNew (Prefs::DSPEED, 0, INT_MAX, 5);
    hig->addRow (l, r);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), r);

  hig->addSectionDivider ();
  QHBoxLayout * h = new QHBoxLayout;
  h->setSpacing (HIG :: PAD);
  QLabel * label = new QLabel;
  label->setPixmap (QPixmap (QString::fromUtf8 (":/icons/alt-limit-off.png")));
  label->setAlignment (Qt::AlignLeft|Qt::AlignVCenter);
  h->addWidget (label);
  label = new QLabel (tr ("Alternative Speed Limits"));
  label->setStyleSheet (QString::fromUtf8 ("font: bold"));
  label->setAlignment (Qt::AlignLeft|Qt::AlignVCenter);
  h->addWidget (label);
  hig->addSectionTitle (h);

    QString s = tr ("<small>Override normal speed limits manually or at scheduled times</small>");
    hig->addWideControl (new QLabel (s));

    s = tr ("U&pload (%1):").arg (speed_K_str);
    r = spinBoxNew (Prefs :: ALT_SPEED_LIMIT_UP, 0, INT_MAX, 5);
    hig->addRow (s, r);

    s = tr ("Do&wnload (%1):").arg (speed_K_str);
    r = spinBoxNew (Prefs :: ALT_SPEED_LIMIT_DOWN, 0, INT_MAX, 5);
    hig->addRow (s, r);

    QCheckBox * c = checkBoxNew (tr ("&Scheduled times:"), Prefs::ALT_SPEED_LIMIT_TIME_ENABLED);
    h = new QHBoxLayout ();
    h->setSpacing (HIG::PAD);
    QWidget * w = timeEditNew (Prefs :: ALT_SPEED_LIMIT_TIME_BEGIN);
    h->addWidget (w, 1);
    mySchedWidgets << w;
    QLabel * nd = new QLabel (tr("&to"));
    h->addWidget (nd);
    mySchedWidgets << nd;
    w = timeEditNew (Prefs :: ALT_SPEED_LIMIT_TIME_END);
    nd->setBuddy (w);
    h->addWidget (w, 1);
    mySchedWidgets << w;
    hig->addRow (c, h, 0);

    s = tr ("&On days:");
    QComboBox * box = new QComboBox;
    const QIcon noIcon;
    box->addItem (noIcon, tr ("Every Day"), QVariant (TR_SCHED_ALL));
    box->addItem (noIcon, tr ("Weekdays"),  QVariant (TR_SCHED_WEEKDAY));
    box->addItem (noIcon, tr ("Weekends"),  QVariant (TR_SCHED_WEEKEND));
    box->addItem (noIcon, tr ("Sunday"),    QVariant (TR_SCHED_SUN));
    box->addItem (noIcon, tr ("Monday"),    QVariant (TR_SCHED_MON));
    box->addItem (noIcon, tr ("Tuesday"),   QVariant (TR_SCHED_TUES));
    box->addItem (noIcon, tr ("Wednesday"), QVariant (TR_SCHED_WED));
    box->addItem (noIcon, tr ("Thursday"),  QVariant (TR_SCHED_THURS));
    box->addItem (noIcon, tr ("Friday"),    QVariant (TR_SCHED_FRI));
    box->addItem (noIcon, tr ("Saturday"),  QVariant (TR_SCHED_SAT));
    box->setCurrentIndex (box->findData (myPrefs.getInt (Prefs :: ALT_SPEED_LIMIT_TIME_DAY)));
    connect (box, SIGNAL(activated(int)), this, SLOT(altSpeedDaysEdited(int)));
    w = hig->addRow (s, box);
    mySchedWidgets << w << box;

  hig->finish ();
  return hig;
}

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

QWidget *
PrefsDialog :: createDesktopTab ()
{
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Desktop"));

    hig->addWideControl (checkBoxNew (tr ("Show Transmission icon in the &notification area"), Prefs::SHOW_TRAY_ICON));
    hig->addWideControl (checkBoxNew (tr ("Start &minimized in notification area"), Prefs::START_MINIMIZED));

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Notification"));

    hig->addWideControl (checkBoxNew (tr ("Show a notification when torrents are a&dded"), Prefs::SHOW_NOTIFICATION_ON_ADD));
    hig->addWideControl (checkBoxNew (tr ("Show a notification when torrents &finish"), Prefs::SHOW_NOTIFICATION_ON_COMPLETE));
    hig->addWideControl (checkBoxNew (tr ("Play a &sound when torrents finish"), Prefs::COMPLETE_SOUND_ENABLED));

  hig->finish ();
  return hig;
}

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

void
PrefsDialog :: onPortTested (bool isOpen)
{
  myPortButton->setEnabled (true);
  myWidgets[Prefs::PEER_PORT]->setEnabled (true);
  myPortLabel->setText (isOpen ? tr ("Port is <b>open</b>")
                               : tr ("Port is <b>closed</b>"));
}

void
PrefsDialog :: onPortTest ()
{
  myPortLabel->setText (tr ("Testing TCP Port..."));
  myPortButton->setEnabled (false);
  myWidgets[Prefs::PEER_PORT]->setEnabled (false);
  mySession.portTest ();
}

QWidget *
PrefsDialog :: createNetworkTab ()
{
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Incoming Peers"));

  QSpinBox * s = spinBoxNew (Prefs::PEER_PORT, 1, 65535, 1);
  QHBoxLayout * h = new QHBoxLayout ();
  QPushButton * b = myPortButton = new QPushButton (tr ("Te&st Port"));
  QLabel * l = myPortLabel = new QLabel (tr ("Status unknown"));
  h->addWidget (l);
  h->addSpacing (HIG :: PAD_BIG);
  h->addWidget (b);
  h->setStretchFactor (l, 1);
  connect (b, SIGNAL(clicked(bool)), this, SLOT(onPortTest()));
  connect (&mySession, SIGNAL(portTested(bool)), this, SLOT(onPortTested(bool)));

  hig->addRow (tr ("&Port for incoming connections:"), s);
  hig->addRow (QString(), h, 0);
  hig->addWideControl (checkBoxNew (tr ("Pick a &random port every time Transmission is started"), Prefs :: PEER_PORT_RANDOM_ON_START));
  hig->addWideControl (checkBoxNew (tr ("Use UPnP or NAT-PMP port &forwarding from my router"), Prefs::PORT_FORWARDING));

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Peer Limits"));
  hig->addRow (tr ("Maximum peers per &torrent:"), spinBoxNew (Prefs::PEER_LIMIT_TORRENT, 1, FD_SETSIZE, 5));
  hig->addRow (tr ("Maximum peers &overall:"), spinBoxNew (Prefs::PEER_LIMIT_GLOBAL, 1, FD_SETSIZE, 5));

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Options"));

  QWidget * w;
  hig->addWideControl (w = checkBoxNew (tr ("Enable &uTP for peer connections"), Prefs::UTP_ENABLED));
  w->setToolTip (tr ("uTP is a tool for reducing network congestion."));
  hig->addWideControl (w = checkBoxNew (tr ("Use PE&X to find more peers"), Prefs::PEX_ENABLED));
  w->setToolTip (tr ("PEX is a tool for exchanging peer lists with the peers you're connected to."));
  hig->addWideControl (w = checkBoxNew (tr ("Use &DHT to find more peers"), Prefs::DHT_ENABLED));
  w->setToolTip (tr ("DHT is a tool for finding peers without a tracker."));
  hig->addWideControl (w = checkBoxNew (tr ("Use &Local Peer Discovery to find more peers"), Prefs::LPD_ENABLED));
  w->setToolTip (tr ("LPD is a tool for finding peers on your local network."));

  hig->finish ();
  return hig;
}

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

void
PrefsDialog :: onBlocklistDialogDestroyed (QObject * o)
{
  Q_UNUSED (o);

  myBlocklistDialog = 0;
}

void
PrefsDialog :: onUpdateBlocklistCancelled ()
{
  disconnect (&mySession, SIGNAL(blocklistUpdated(int)), this, SLOT(onBlocklistUpdated(int)));
  myBlocklistDialog->deleteLater ();
}

void
PrefsDialog :: onBlocklistUpdated (int n)
{
  myBlocklistDialog->setText (tr ("<b>Update succeeded!</b><p>Blocklist now has %Ln rules.", 0, n));
  myBlocklistDialog->setTextFormat (Qt::RichText);
}

void
PrefsDialog :: onUpdateBlocklistClicked ()
{
  myBlocklistDialog = new QMessageBox (QMessageBox::Information,
                                       QString(),
                                       tr ("<b>Update Blocklist</b><p>Getting new blocklist..."),
                                       QMessageBox::Close,
                                       this);
  connect (myBlocklistDialog, SIGNAL(rejected()), this, SLOT(onUpdateBlocklistCancelled()));
  connect (&mySession, SIGNAL(blocklistUpdated(int)), this, SLOT(onBlocklistUpdated(int)));
  myBlocklistDialog->show ();
  mySession.updateBlocklist ();
}

void
PrefsDialog :: encryptionEdited (int i)
{
  const int value (qobject_cast<QComboBox*>(sender())->itemData(i).toInt ());
  setPref (Prefs::ENCRYPTION, value);
}

QWidget *
PrefsDialog :: createPrivacyTab ()
{
  QWidget * w;
  HIG * hig = new HIG (this);

  hig->addSectionTitle (tr ("Encryption"));

  QComboBox * box = new QComboBox ();
  box->addItem (tr ("Allow encryption"), 0);
  box->addItem (tr ("Prefer encryption"), 1);
  box->addItem (tr ("Require encryption"), 2);
  myWidgets.insert (Prefs :: ENCRYPTION, box);
  connect (box, SIGNAL(activated(int)), this, SLOT(encryptionEdited(int)));

  hig->addRow (tr ("&Encryption mode:"), box);

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Blocklist"));

  QWidget * l = checkBoxNew (tr("Enable &blocklist:"), Prefs::BLOCKLIST_ENABLED);
  QWidget * e = lineEditNew (Prefs::BLOCKLIST_URL);
  myBlockWidgets << e;
  hig->addRow (l, e);

  l = myBlocklistLabel = new QLabel ();
  myBlockWidgets << l;
  w = new QPushButton (tr ("&Update"));
  connect (w, SIGNAL(clicked(bool)), this, SLOT(onUpdateBlocklistClicked()));
  myBlockWidgets << w;
  QHBoxLayout * h = new QHBoxLayout ();
  h->addWidget (l);
  h->addStretch (1);
  h->addWidget (w);
  hig->addWideControl (h);

  l = checkBoxNew (tr ("Enable &automatic updates"), Prefs::BLOCKLIST_UPDATES_ENABLED);
  myBlockWidgets << l;
  hig->addWideControl (l);

  hig->finish ();
  updateBlocklistLabel ();
  return hig;
}

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

void
PrefsDialog :: onScriptClicked (void)
{
  const QString title = tr ("Select \"Torrent Done\" Script");
  const QString myPath = myPrefs.getString (Prefs::SCRIPT_TORRENT_DONE_FILENAME);
  const QString path = Utils::remoteFileChooser (this, title, myPath, false, mySession.isServer());

  if (!path.isEmpty())
    onLocationSelected (path, Prefs::SCRIPT_TORRENT_DONE_FILENAME);
}

void
PrefsDialog :: onIncompleteClicked (void)
{
  const QString title = tr ("Select Incomplete Directory");
  const QString myPath = myPrefs.getString (Prefs::INCOMPLETE_DIR);
  const QString path = Utils::remoteFileChooser (this, title, myPath, true, mySession.isServer());

  if (!path.isEmpty())
    onLocationSelected (path, Prefs::INCOMPLETE_DIR);
}

void
PrefsDialog :: onWatchClicked (void)
{
  const QString title = tr ("Select Watch Directory");
  const QString myPath = myPrefs.getString (Prefs::DIR_WATCH);
  const QString path = Utils::remoteFileChooser (this, title, myPath, true, true);

  if (!path.isEmpty())
    onLocationSelected (path, Prefs::DIR_WATCH);
}

void
PrefsDialog :: onDestinationClicked (void)
{
  const QString title = tr ("Select Destination");
  const QString myPath = myPrefs.getString (Prefs::DOWNLOAD_DIR);
  const QString path = Utils::remoteFileChooser (this, title, myPath, true, mySession.isServer());

  if (!path.isEmpty())
    onLocationSelected (path, Prefs::DOWNLOAD_DIR);
}

void
PrefsDialog :: onLocationSelected (const QString& path, int key)
{
  setPref (key, path);
}

QWidget *
PrefsDialog :: createSeedingTab ()
{
  const int iconSize (style ()->pixelMetric (QStyle :: PM_SmallIconSize));
  const QFileIconProvider iconProvider;
  const QIcon folderIcon = iconProvider.icon (QFileIconProvider::Folder);
  const QPixmap folderPixmap = folderIcon.pixmap (iconSize);
  const QIcon fileIcon = iconProvider.icon (QFileIconProvider::File);
  const QPixmap filePixmap = fileIcon.pixmap (iconSize);

  QWidget *l, *r;
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Limits"));

    l = checkBoxNew (tr ("Stop seeding at &ratio:"), Prefs::RATIO_ENABLED);
    r = doubleSpinBoxNew (Prefs::RATIO, 0, INT_MAX, 0.5, 2);
    hig->addRow (l, r);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), r);

    l = checkBoxNew (tr ("Stop seeding if idle for &N minutes:"), Prefs::IDLE_LIMIT_ENABLED);
    r = spinBoxNew (Prefs::IDLE_LIMIT, 1, INT_MAX, 5);
    hig->addRow (l, r);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), r);

  hig->finish ();
  return hig;
}

QWidget *
PrefsDialog :: createDownloadingTab ()
{
  const int iconSize (style ()->pixelMetric (QStyle :: PM_SmallIconSize));
  const QFileIconProvider iconProvider;
  const QIcon folderIcon = iconProvider.icon (QFileIconProvider::Folder);
  const QPixmap folderPixmap = folderIcon.pixmap (iconSize);
  const QIcon fileIcon = iconProvider.icon (QFileIconProvider::File);
  const QPixmap filePixmap = fileIcon.pixmap (iconSize);

  QWidget * l;
  QPushButton * b;
  HIG * hig = new HIG (this);
  hig->addSectionTitle (tr ("Adding"));

    l = checkBoxNew (tr ("Automatically add .torrent files &from:"), Prefs::DIR_WATCH_ENABLED);
    b = myWatchButton = new QPushButton;
    b->setIcon (folderPixmap);
    b->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
    connect (b, SIGNAL(clicked(bool)), this, SLOT(onWatchClicked(void)));
    hig->addRow (l, b);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), b);

    hig->addWideControl (checkBoxNew (tr ("Show the Torrent Options &dialog"), Prefs::OPTIONS_PROMPT));

    hig->addWideControl (checkBoxNew (tr ("&Start added torrents"), Prefs::START));

    hig->addWideControl (checkBoxNew (tr ("Mo&ve the .torrent file to the trash"), Prefs::TRASH_ORIGINAL));

    b = myDestinationButton = new QPushButton;
    b->setIcon (folderPixmap);
    b->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
    connect (b, SIGNAL(clicked(bool)), this, SLOT(onDestinationClicked(void)));
    hig->addRow (tr ("Save to &Location:"), b);

    const QString downloadDir (myPrefs.getString(Prefs::DOWNLOAD_DIR));
    l = myFreespaceLabel = new FreespaceLabel (mySession, downloadDir, this);
    QHBoxLayout * h = new QHBoxLayout ();
    h->addStretch (1);
    h->addWidget (l);
    hig->addWideControl (h);

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Download Queue"));
    
    hig->addRow (tr ("Ma&ximum active downloads:"), spinBoxNew (Prefs::DOWNLOAD_QUEUE_SIZE, 1, INT_MAX, 1));
    hig->addRow (tr ("Downloads sharing data in the last &N minutes are active:"), spinBoxNew (Prefs::QUEUE_STALLED_MINUTES, 1, INT_MAX, 10));

  hig->addSectionDivider ();
  hig->addSectionTitle (tr ("Incomplete"));

    hig->addWideControl (checkBoxNew (tr ("Append \".&part\" to incomplete files' names"), Prefs::RENAME_PARTIAL_FILES));

    l = myIncompleteCheckbox = checkBoxNew (tr ("Keep &incomplete files in:"), Prefs::INCOMPLETE_DIR_ENABLED);
    b = myIncompleteButton = new QPushButton;
    b->setIcon (folderPixmap);
    b->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
    connect (b, SIGNAL(clicked(bool)), this, SLOT(onIncompleteClicked(void)));
    hig->addRow (myIncompleteCheckbox, b);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), b);

    l = myTorrentDoneScriptCheckbox = checkBoxNew (tr ("Call scrip&t when torrent is completed:"), Prefs::SCRIPT_TORRENT_DONE_ENABLED);
    b = myTorrentDoneScriptButton = new QPushButton;
    b->setIcon (filePixmap);
    b->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
    connect (b, SIGNAL(clicked(bool)), this, SLOT(onScriptClicked(void)));
    hig->addRow (myTorrentDoneScriptCheckbox, b);
    enableBuddyWhenChecked (qobject_cast<QCheckBox*>(l), b);

  hig->finish ();
  return hig;
}

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

PrefsDialog :: PrefsDialog (Session& session, Prefs& prefs, QWidget * parent):
  QDialog (parent),
  myIsServer (session.isServer ()),
  mySession (session),
  myPrefs (prefs),
  myLayout (new QVBoxLayout (this))
{
  setWindowTitle (tr ("Transmission Preferences"));

  QTabWidget * t = new QTabWidget (this);
  t->addTab (createSpeedTab (),        tr ("Speed"));
  t->addTab (createDownloadingTab (),  tr ("Downloading"));
  t->addTab (createSeedingTab (),      tr ("Seeding"));
  t->addTab (createPrivacyTab (),      tr ("Privacy"));
  t->addTab (createNetworkTab (),      tr ("Network"));
  t->addTab (createDesktopTab (),      tr ("Desktop"));
  t->addTab (createRemoteTab(session), tr ("Remote"));
  myLayout->addWidget (t);

  QDialogButtonBox * buttons = new QDialogButtonBox (QDialogButtonBox::Close, Qt::Horizontal, this);
  connect (buttons, SIGNAL(rejected()), this, SLOT(close())); // "close" triggers rejected
  myLayout->addWidget (buttons);
  QWidget::setAttribute (Qt::WA_DeleteOnClose, true);

  connect (&mySession, SIGNAL(sessionUpdated()), this, SLOT(sessionUpdated()));

  QList<int> keys;
  keys << Prefs :: RPC_ENABLED
       << Prefs :: ALT_SPEED_LIMIT_ENABLED
       << Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED
       << Prefs :: ENCRYPTION
       << Prefs :: BLOCKLIST_ENABLED
       << Prefs :: DIR_WATCH
       << Prefs :: DOWNLOAD_DIR
       << Prefs :: INCOMPLETE_DIR
       << Prefs :: INCOMPLETE_DIR_ENABLED
       << Prefs :: SCRIPT_TORRENT_DONE_FILENAME;
  foreach (int key, keys)
    refreshPref (key);

  // if it's a remote session, disable the preferences
  // that don't work in remote sessions
  if (!myIsServer)
    {
      foreach (QWidget * w, myUnsupportedWhenRemote)
        {
          w->setToolTip (tr ("Not supported by remote sessions"));
          w->setEnabled (false);
        }
    }
}

PrefsDialog :: ~PrefsDialog ()
{
}

void
PrefsDialog :: setPref (int key, const QVariant& v)
{
  myPrefs.set (key, v);
  refreshPref (key);
}

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

void
PrefsDialog :: sessionUpdated ()
{
  updateBlocklistLabel ();
}

void
PrefsDialog :: updateBlocklistLabel ()
{
  const int n = mySession.blocklistSize ();
  myBlocklistLabel->setText (tr ("<i>Blocklist contains %Ln rules</i>", 0, n));
}

void
PrefsDialog :: refreshPref (int key)
{
  switch (key)
    {
      case Prefs :: RPC_ENABLED:
      case Prefs :: RPC_WHITELIST_ENABLED:
      case Prefs :: RPC_AUTH_REQUIRED:
        {
          const bool enabled (myPrefs.getBool (Prefs::RPC_ENABLED));
          const bool whitelist (myPrefs.getBool (Prefs::RPC_WHITELIST_ENABLED));
          const bool auth (myPrefs.getBool (Prefs::RPC_AUTH_REQUIRED));
          foreach (QWidget * w, myWebWhitelistWidgets)w->setEnabled (enabled && whitelist);
          foreach (QWidget * w, myWebAuthWidgets)w->setEnabled (enabled && auth);
          foreach (QWidget * w, myWebWidgets)w->setEnabled (enabled);
          break;
        }

      case Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED:
        {
          const bool enabled = myPrefs.getBool (key);
          foreach (QWidget * w, mySchedWidgets)w->setEnabled (enabled);
          break;
        }

      case Prefs :: BLOCKLIST_ENABLED:
        {
          const bool enabled = myPrefs.getBool (key);
          foreach (QWidget * w, myBlockWidgets)w->setEnabled (enabled);
          break;
        }

      case Prefs :: DIR_WATCH:
        myWatchButton->setText (QFileInfo(myPrefs.getString(Prefs::DIR_WATCH)).fileName());
        break;

      case Prefs :: SCRIPT_TORRENT_DONE_FILENAME:
        {
          const QString path (myPrefs.getString (key));
          myTorrentDoneScriptButton->setText (QFileInfo(path).fileName());
          break;
        }

      case Prefs :: PEER_PORT:
        myPortLabel->setText (tr ("Status unknown"));
        myPortButton->setEnabled (true);
        break;

      case Prefs :: DOWNLOAD_DIR:
        {
          const QString path (myPrefs.getString (key));
          myDestinationButton->setText (QFileInfo(path).fileName());
          myFreespaceLabel->setPath (path);
          break;
        }

      case Prefs :: INCOMPLETE_DIR:
        {
          QString path (myPrefs.getString (key));
          myIncompleteButton->setText (QFileInfo(path).fileName());
          break;
        }

      case Prefs :: INCOMPLETE_DIR_ENABLED:
        {
          const bool enabled = myPrefs.getBool (key);
          myIncompleteButton->setEnabled (enabled);
          break;
        }

      default:
        break;
    }

  key2widget_t::iterator it (myWidgets.find (key));
  if (it != myWidgets.end ())
    {
      QWidget * w (it.value ());
      QCheckBox * checkBox;
      QSpinBox * spin;
      QDoubleSpinBox * doubleSpin;
      QTimeEdit * timeEdit;
      QLineEdit * lineEdit;

      if ((checkBox = qobject_cast<QCheckBox*>(w)))
        {
          checkBox->setChecked (myPrefs.getBool (key));
        }
      else if ((spin = qobject_cast<QSpinBox*>(w)))
        {
          spin->setValue (myPrefs.getInt (key));
        }
      else if ((doubleSpin = qobject_cast<QDoubleSpinBox*>(w)))
        {
          doubleSpin->setValue (myPrefs.getDouble (key));
        }
      else if ((timeEdit = qobject_cast<QTimeEdit*>(w)))
        {
          const int minutes (myPrefs.getInt (key));
          timeEdit->setTime (QTime().addSecs (minutes * 60));
        }
      else if ((lineEdit = qobject_cast<QLineEdit*>(w)))
        {
          lineEdit->setText (myPrefs.getString (key));
        }
      else if (key == Prefs::ENCRYPTION)
        {
          QComboBox * comboBox (qobject_cast<QComboBox*> (w));
          const int index = comboBox->findData (myPrefs.getInt (key));
          comboBox->setCurrentIndex (index);
        }
    }
}

bool
PrefsDialog :: isAllowed (int key) const
{
  Q_UNUSED (key);

  return true;
}
