461 lines
13 KiB
C++
461 lines
13 KiB
C++
/*
|
|
* 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$
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
#include <QCheckBox>
|
|
#include <QDialogButtonBox>
|
|
#include <QFileDialog>
|
|
#include <QFileIconProvider>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QList>
|
|
#include <QMimeData>
|
|
#include <QPlainTextEdit>
|
|
#include <QProgressBar>
|
|
#include <QPushButton>
|
|
#include <QRadioButton>
|
|
#include <QSize>
|
|
#include <QStyle>
|
|
#include <QTimer>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/makemeta.h>
|
|
#include <libtransmission/utils.h>
|
|
|
|
#include "formatter.h"
|
|
#include "hig.h"
|
|
#include "make-dialog.h"
|
|
#include "session.h"
|
|
#include "utils.h"
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MakeDialog :: onNewDialogDestroyed (QObject * o)
|
|
{
|
|
Q_UNUSED (o);
|
|
|
|
myTimer.stop ();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onNewButtonBoxClicked (QAbstractButton * button)
|
|
{
|
|
switch (myNewButtonBox->standardButton (button))
|
|
{
|
|
case QDialogButtonBox::Open:
|
|
mySession.addNewlyCreatedTorrent (myTarget, QFileInfo(QString::fromUtf8(myBuilder->top)).dir().path());
|
|
break;
|
|
|
|
case QDialogButtonBox::Abort:
|
|
myBuilder->abortFlag = true;
|
|
break;
|
|
|
|
default: // QDialogButtonBox::Ok:
|
|
break;
|
|
}
|
|
|
|
myNewDialog->deleteLater ();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onProgress ()
|
|
{
|
|
// progress bar
|
|
const tr_metainfo_builder * b = myBuilder;
|
|
const double denom = b->pieceCount ? b->pieceCount : 1;
|
|
myNewProgress->setValue ((int) ((100.0 * b->pieceIndex) / denom));
|
|
|
|
// progress label
|
|
const QString top = QString::fromLocal8Bit (myBuilder->top);
|
|
const QString base (QFileInfo(top).completeBaseName());
|
|
QString str;
|
|
if (!b->isDone)
|
|
str = tr ("Creating \"%1\"").arg (base);
|
|
else if (b->result == TR_MAKEMETA_OK)
|
|
str = tr ("Created \"%1\"!").arg (base);
|
|
else if (b->result == TR_MAKEMETA_URL)
|
|
str = tr ("Error: invalid announce URL \"%1\"").arg (QString::fromLocal8Bit (b->errfile));
|
|
else if (b->result == TR_MAKEMETA_CANCELLED)
|
|
str = tr ("Cancelled");
|
|
else if (b->result == TR_MAKEMETA_IO_READ)
|
|
str = tr ("Error reading \"%1\": %2").arg (QString::fromLocal8Bit(b->errfile)).arg (QString::fromLocal8Bit(strerror(b->my_errno)));
|
|
else if (b->result == TR_MAKEMETA_IO_WRITE)
|
|
str = tr ("Error writing \"%1\": %2").arg (QString::fromLocal8Bit(b->errfile)).arg (QString::fromLocal8Bit(strerror(b->my_errno)));
|
|
myNewLabel->setText (str);
|
|
|
|
// buttons
|
|
(myNewButtonBox->button(QDialogButtonBox::Abort))->setEnabled (!b->isDone);
|
|
(myNewButtonBox->button(QDialogButtonBox::Ok))->setEnabled (b->isDone);
|
|
(myNewButtonBox->button(QDialogButtonBox::Open))->setEnabled (b->isDone && !b->result);
|
|
}
|
|
|
|
|
|
void
|
|
MakeDialog :: makeTorrent ()
|
|
{
|
|
if (!myBuilder)
|
|
return;
|
|
|
|
// get the tiers
|
|
int tier = 0;
|
|
QVector<tr_tracker_info> trackers;
|
|
foreach (QString line, myTrackerEdit->toPlainText().split("\n"))
|
|
{
|
|
line = line.trimmed ();
|
|
if (line.isEmpty ())
|
|
{
|
|
++tier;
|
|
}
|
|
else
|
|
{
|
|
tr_tracker_info tmp;
|
|
tmp.announce = tr_strdup (line.toUtf8().constData ());
|
|
tmp.tier = tier;
|
|
trackers.append (tmp);
|
|
}
|
|
}
|
|
|
|
// pop up the dialog
|
|
QDialog * dialog = new QDialog (this);
|
|
dialog->setWindowTitle (tr ("New Torrent"));
|
|
myNewDialog = dialog;
|
|
QVBoxLayout * top = new QVBoxLayout (dialog);
|
|
top->addWidget( (myNewLabel = new QLabel));
|
|
top->addWidget( (myNewProgress = new QProgressBar));
|
|
QDialogButtonBox * buttons = new QDialogButtonBox (QDialogButtonBox::Ok
|
|
| QDialogButtonBox::Open
|
|
| QDialogButtonBox::Abort);
|
|
myNewButtonBox = buttons;
|
|
connect (buttons, SIGNAL(clicked(QAbstractButton*)),
|
|
this, SLOT(onNewButtonBoxClicked(QAbstractButton*)));
|
|
top->addWidget (buttons);
|
|
onProgress ();
|
|
dialog->show ();
|
|
connect (dialog, SIGNAL(destroyed(QObject*)),
|
|
this, SLOT(onNewDialogDestroyed(QObject*)));
|
|
myTimer.start (100);
|
|
|
|
// the file to create
|
|
const QString path = QString::fromUtf8 (myBuilder->top);
|
|
const QString torrentName = QFileInfo(path).completeBaseName() + ".torrent";
|
|
myTarget = QDir (myDestination).filePath (torrentName);
|
|
|
|
// comment
|
|
QString comment;
|
|
if (myCommentCheck->isChecked())
|
|
comment = myCommentEdit->text();
|
|
|
|
// start making the torrent
|
|
tr_makeMetaInfo (myBuilder,
|
|
myTarget.toUtf8().constData(),
|
|
(trackers.isEmpty() ? NULL : trackers.data()),
|
|
trackers.size(),
|
|
(comment.isEmpty() ? NULL : comment.toUtf8().constData()),
|
|
myPrivateCheck->isChecked());
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MakeDialog :: onFileClicked ()
|
|
{
|
|
QFileDialog * d = new QFileDialog (this, tr ("Select File"));
|
|
d->setFileMode (QFileDialog::ExistingFile);
|
|
d->setAttribute (Qt::WA_DeleteOnClose);
|
|
connect (d, SIGNAL(filesSelected(const QStringList&)),
|
|
this, SLOT(onFileSelected(const QStringList&)));
|
|
d->show ();
|
|
}
|
|
void
|
|
MakeDialog :: onFileSelected (const QStringList& list)
|
|
{
|
|
if (!list.empty ())
|
|
onFileSelected (list.front ());
|
|
}
|
|
void
|
|
MakeDialog :: onFileSelected (const QString& filename)
|
|
{
|
|
myFile = Utils::removeTrailingDirSeparator (filename);
|
|
myFileButton->setText (QFileInfo(myFile).fileName());
|
|
onSourceChanged ();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onFolderClicked ()
|
|
{
|
|
QFileDialog * d = new QFileDialog (this, tr ("Select Folder"));
|
|
d->setFileMode (QFileDialog::Directory);
|
|
d->setOption (QFileDialog::ShowDirsOnly);
|
|
d->setAttribute (Qt::WA_DeleteOnClose);
|
|
connect (d, SIGNAL(filesSelected(const QStringList&)),
|
|
this, SLOT(onFolderSelected(const QStringList&)));
|
|
d->show ();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onFolderSelected (const QStringList& list)
|
|
{
|
|
if (!list.empty ())
|
|
onFolderSelected (list.front ());
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onFolderSelected (const QString& filename)
|
|
{
|
|
myFolder = Utils::removeTrailingDirSeparator (filename);
|
|
myFolderButton->setText (QFileInfo(myFolder).fileName());
|
|
onSourceChanged ();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onDestinationClicked ()
|
|
{
|
|
QFileDialog * d = new QFileDialog (this, tr ("Select Folder"));
|
|
d->setFileMode (QFileDialog::Directory);
|
|
d->setOption (QFileDialog::ShowDirsOnly);
|
|
d->setAttribute (Qt::WA_DeleteOnClose);
|
|
connect (d, SIGNAL(filesSelected(const QStringList&)),
|
|
this, SLOT(onDestinationSelected(const QStringList&)));
|
|
d->show ();
|
|
}
|
|
void
|
|
MakeDialog :: onDestinationSelected (const QStringList& list)
|
|
{
|
|
if (!list.empty ())
|
|
onDestinationSelected (list.front());
|
|
}
|
|
void
|
|
MakeDialog :: onDestinationSelected (const QString& filename)
|
|
{
|
|
myDestination = Utils::removeTrailingDirSeparator (filename);
|
|
myDestinationButton->setText (QFileInfo(myDestination).fileName());
|
|
}
|
|
|
|
void
|
|
MakeDialog :: enableBuddyWhenChecked (QRadioButton * box, QWidget * buddy)
|
|
{
|
|
connect (box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)));
|
|
buddy->setEnabled (box->isChecked ());
|
|
}
|
|
void
|
|
MakeDialog :: enableBuddyWhenChecked (QCheckBox * box, QWidget * buddy)
|
|
{
|
|
connect (box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)));
|
|
buddy->setEnabled (box->isChecked ());
|
|
}
|
|
|
|
QString
|
|
MakeDialog :: getSource () const
|
|
{
|
|
return myFileRadio->isChecked () ? myFile : myFolder;
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onButtonBoxClicked (QAbstractButton * button)
|
|
{
|
|
switch (myButtonBox->standardButton (button))
|
|
{
|
|
case QDialogButtonBox::Ok:
|
|
makeTorrent ();
|
|
break;
|
|
|
|
default: // QDialogButtonBox::Close:
|
|
deleteLater ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MakeDialog :: onSourceChanged ()
|
|
{
|
|
if (myBuilder)
|
|
{
|
|
tr_metaInfoBuilderFree (myBuilder);
|
|
myBuilder = 0;
|
|
}
|
|
|
|
const QString filename = getSource ();
|
|
if (!filename.isEmpty ())
|
|
myBuilder = tr_metaInfoBuilderCreate (filename.toUtf8().constData());
|
|
|
|
QString text;
|
|
if (!myBuilder)
|
|
{
|
|
text = tr ("<i>No source selected<i>");
|
|
}
|
|
else
|
|
{
|
|
QString files = tr ("%Ln File(s)", 0, myBuilder->fileCount);
|
|
QString pieces = tr ("%Ln Piece(s)", 0, myBuilder->pieceCount);
|
|
text = tr ("%1 in %2; %3 @ %4")
|
|
.arg (Formatter::sizeToString (myBuilder->totalSize))
|
|
.arg (files)
|
|
.arg (pieces)
|
|
.arg (Formatter::sizeToString (myBuilder->pieceSize));
|
|
}
|
|
|
|
mySourceLabel->setText (text);
|
|
}
|
|
|
|
|
|
// bah, there doesn't seem to be any cleaner way to override
|
|
// QPlainTextEdit's default desire to be 12 lines tall
|
|
class ShortPlainTextEdit: public QPlainTextEdit
|
|
{
|
|
public:
|
|
virtual ~ShortPlainTextEdit () {}
|
|
ShortPlainTextEdit (QWidget * parent = 0): QPlainTextEdit(parent) {}
|
|
virtual QSize sizeHint () const { return QSize (256, 50); }
|
|
};
|
|
|
|
MakeDialog :: MakeDialog (Session & session, QWidget * parent):
|
|
QDialog (parent, Qt::Dialog),
|
|
mySession (session),
|
|
myBuilder (0)
|
|
{
|
|
setAcceptDrops (true);
|
|
|
|
connect (&myTimer, SIGNAL(timeout()), this, SLOT(onProgress()));
|
|
|
|
setWindowTitle (tr ("New Torrent"));
|
|
QVBoxLayout * top = new QVBoxLayout (this);
|
|
top->setSpacing (HIG :: PAD);
|
|
|
|
HIG * hig = new HIG;
|
|
hig->setContentsMargins (0, 0, 0, 0);
|
|
hig->addSectionTitle (tr ("Files"));
|
|
|
|
QFileIconProvider iconProvider;
|
|
const int iconSize (style()->pixelMetric (QStyle::PM_SmallIconSize));
|
|
const QIcon folderIcon = iconProvider.icon (QFileIconProvider::Folder);
|
|
const QPixmap folderPixmap = folderIcon.pixmap (iconSize);
|
|
QPushButton * b = new QPushButton;
|
|
b->setIcon (folderPixmap);
|
|
b->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
|
|
myDestination = QDir::homePath();
|
|
b->setText (myDestination);
|
|
connect (b, SIGNAL(clicked(bool)),
|
|
this, SLOT(onDestinationClicked(void)));
|
|
myDestinationButton = b;
|
|
hig->addRow (tr ("Sa&ve to:"), b);
|
|
|
|
myFolderRadio = new QRadioButton (tr ("Source F&older:"));
|
|
connect (myFolderRadio, SIGNAL(toggled(bool)),
|
|
this, SLOT(onSourceChanged()));
|
|
myFolderButton = new QPushButton;
|
|
myFolderButton->setIcon (folderPixmap);
|
|
myFolderButton->setText (tr ("(None)"));
|
|
myFolderButton->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
|
|
connect (myFolderButton, SIGNAL(clicked(bool)),
|
|
this, SLOT(onFolderClicked(void)));
|
|
hig->addRow (myFolderRadio, myFolderButton);
|
|
enableBuddyWhenChecked (myFolderRadio, myFolderButton);
|
|
|
|
const QIcon fileIcon = iconProvider.icon (QFileIconProvider::File);
|
|
const QPixmap filePixmap = fileIcon.pixmap (iconSize);
|
|
myFileRadio = new QRadioButton (tr ("Source &File:"));
|
|
myFileRadio->setChecked (true);
|
|
connect (myFileRadio, SIGNAL(toggled(bool)),
|
|
this, SLOT(onSourceChanged()));
|
|
myFileButton = new QPushButton;
|
|
myFileButton->setText (tr ("(None)"));
|
|
myFileButton->setIcon (filePixmap);
|
|
myFileButton->setStyleSheet (QString::fromUtf8 ("text-align: left; padding-left: 5; padding-right: 5"));
|
|
connect (myFileButton, SIGNAL(clicked(bool)),
|
|
this, SLOT(onFileClicked(void)));
|
|
hig->addRow (myFileRadio, myFileButton);
|
|
enableBuddyWhenChecked (myFileRadio, myFileButton);
|
|
|
|
mySourceLabel = new QLabel (this);
|
|
hig->addRow (tr (""), mySourceLabel);
|
|
|
|
hig->addSectionDivider ();
|
|
hig->addSectionTitle (tr ("Properties"));
|
|
|
|
hig->addWideControl (myTrackerEdit = new ShortPlainTextEdit);
|
|
const int height = fontMetrics().size (0, QString::fromUtf8("\n\n\n\n")).height ();
|
|
myTrackerEdit->setMinimumHeight (height);
|
|
hig->addTallRow (tr ("&Trackers:"), myTrackerEdit);
|
|
QLabel * l = new QLabel (tr ("To add a backup URL, add it on the line after the primary URL.\nTo add another primary URL, add it after a blank line."));
|
|
l->setAlignment (Qt::AlignLeft);
|
|
hig->addRow (tr (""), l);
|
|
myTrackerEdit->resize (500, height);
|
|
|
|
myCommentCheck = new QCheckBox (tr ("Co&mment"));
|
|
myCommentEdit = new QLineEdit ();
|
|
hig->addRow (myCommentCheck, myCommentEdit);
|
|
enableBuddyWhenChecked (myCommentCheck, myCommentEdit);
|
|
|
|
myPrivateCheck = hig->addWideCheckBox (tr ("&Private torrent"), false);
|
|
|
|
hig->finish ();
|
|
top->addWidget (hig, 1);
|
|
|
|
myButtonBox = new QDialogButtonBox (QDialogButtonBox::Ok
|
|
| QDialogButtonBox::Close);
|
|
connect (myButtonBox, SIGNAL(clicked(QAbstractButton*)),
|
|
this, SLOT(onButtonBoxClicked(QAbstractButton*)));
|
|
|
|
top->addWidget (myButtonBox);
|
|
onSourceChanged ();
|
|
}
|
|
|
|
MakeDialog :: ~MakeDialog ()
|
|
{
|
|
if (myBuilder)
|
|
tr_metaInfoBuilderFree (myBuilder);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MakeDialog :: dragEnterEvent (QDragEnterEvent * event)
|
|
{
|
|
const QMimeData * mime = event->mimeData ();
|
|
|
|
if (mime->urls().size() && QFile(mime->urls().front().path()).exists ())
|
|
event->acceptProposedAction();
|
|
}
|
|
|
|
void
|
|
MakeDialog :: dropEvent (QDropEvent * event)
|
|
{
|
|
const QString filename = event->mimeData()->urls().front().path();
|
|
const QFileInfo fileInfo (filename);
|
|
|
|
if (fileInfo.exists ())
|
|
{
|
|
if (fileInfo.isDir ())
|
|
{
|
|
myFolderRadio->setChecked (true);
|
|
onFolderSelected (filename );
|
|
}
|
|
else // it's a file
|
|
{
|
|
myFileRadio->setChecked (true);
|
|
onFileSelected (filename);
|
|
}
|
|
}
|
|
}
|