454 lines
14 KiB
C++
454 lines
14 KiB
C++
/*
|
|
* 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$
|
|
*/
|
|
|
|
#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"
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MakeDialog :: onNewDialogDestroyed( QObject * o )
|
|
{
|
|
Q_UNUSED( o );
|
|
|
|
myTimer.stop( );
|
|
}
|
|
|
|
void
|
|
MakeDialog :: onNewButtonBoxClicked( QAbstractButton * button )
|
|
{
|
|
switch( myNewButtonBox->standardButton( button ) )
|
|
{
|
|
case QDialogButtonBox::Open: {
|
|
const QString top = QString::fromLocal8Bit( myBuilder->top );
|
|
std::cerr << "calling mySession.addTorrent( " << qPrintable(myTarget) << ", " << qPrintable(QFileInfo(top).dir().path()) << ')' << std::endl;
|
|
mySession.addNewlyCreatedTorrent( myTarget, QFileInfo(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 = 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 = 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 = 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 );
|
|
}
|
|
}
|
|
}
|