1/* 2 * This file Copyright (C) Mnemosyne LLC 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 6 * as published by the Free Software Foundation. 7 * 8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 9 * 10 * $Id: make-dialog.cc 13555 2012-10-08 04:23:39Z jordan $ 11 */ 12 13#include <cassert> 14#include <iostream> 15 16#include <QCheckBox> 17#include <QDialogButtonBox> 18#include <QFileDialog> 19#include <QFileIconProvider> 20#include <QHBoxLayout> 21#include <QLabel> 22#include <QLineEdit> 23#include <QList> 24#include <QPlainTextEdit> 25#include <QProgressBar> 26#include <QPushButton> 27#include <QRadioButton> 28#include <QSize> 29#include <QStyle> 30#include <QTimer> 31#include <QVBoxLayout> 32 33#include <libtransmission/transmission.h> 34#include <libtransmission/makemeta.h> 35#include <libtransmission/utils.h> 36 37#include "formatter.h" 38#include "hig.h" 39#include "make-dialog.h" 40#include "session.h" 41 42/*** 43**** 44***/ 45 46void 47MakeDialog :: onNewDialogDestroyed( QObject * o ) 48{ 49 Q_UNUSED( o ); 50 51 myTimer.stop( ); 52} 53 54void 55MakeDialog :: onNewButtonBoxClicked( QAbstractButton * button ) 56{ 57 switch( myNewButtonBox->standardButton( button ) ) 58 { 59 case QDialogButtonBox::Open: { 60 const QString top = QString::fromLocal8Bit( myBuilder->top ); 61std::cerr << "calling mySession.addTorrent( " << qPrintable(myTarget) << ", " << qPrintable(QFileInfo(top).dir().path()) << ')' << std::endl; 62 mySession.addNewlyCreatedTorrent( myTarget, QFileInfo(top).dir().path() ); 63 break; 64 } 65 case QDialogButtonBox::Abort: 66 myBuilder->abortFlag = true; 67 break; 68 default: // QDialogButtonBox::Ok: 69 break; 70 71 } 72 myNewDialog->deleteLater( ); 73} 74 75void 76MakeDialog :: onProgress( ) 77{ 78 // progress bar 79 const tr_metainfo_builder * b = myBuilder; 80 const double denom = b->pieceCount ? b->pieceCount : 1; 81 myNewProgress->setValue( (int) ((100.0 * b->pieceIndex) / denom ) ); 82 83 // progress label 84 const QString top = QString::fromLocal8Bit( myBuilder->top ); 85 const QString base( QFileInfo(top).completeBaseName() ); 86 QString str; 87 if( !b->isDone ) 88 str = tr( "Creating \"%1\"" ).arg( base ); 89 else if( b->result == TR_MAKEMETA_OK ) 90 str = tr( "Created \"%1\"!" ).arg( base ); 91 else if( b->result == TR_MAKEMETA_URL ) 92 str = tr( "Error: invalid announce URL \"%1\"" ).arg( QString::fromLocal8Bit( b->errfile ) ); 93 else if( b->result == TR_MAKEMETA_CANCELLED ) 94 str = tr( "Cancelled" ); 95 else if( b->result == TR_MAKEMETA_IO_READ ) 96 str = tr( "Error reading \"%1\": %2" ).arg( QString::fromLocal8Bit(b->errfile) ).arg( QString::fromLocal8Bit(strerror(b->my_errno)) ); 97 else if( b->result == TR_MAKEMETA_IO_WRITE ) 98 str = tr( "Error writing \"%1\": %2" ).arg( QString::fromLocal8Bit(b->errfile) ).arg( QString::fromLocal8Bit(strerror(b->my_errno)) ); 99 myNewLabel->setText( str ); 100 101 // buttons 102 (myNewButtonBox->button(QDialogButtonBox::Abort))->setEnabled( !b->isDone ); 103 (myNewButtonBox->button(QDialogButtonBox::Ok))->setEnabled( b->isDone ); 104 (myNewButtonBox->button(QDialogButtonBox::Open))->setEnabled( b->isDone && !b->result ); 105} 106 107 108void 109MakeDialog :: makeTorrent( ) 110{ 111 if( !myBuilder ) 112 return; 113 114 // get the tiers 115 int tier = 0; 116 QList<tr_tracker_info> trackers; 117 foreach( QString line, myTrackerEdit->toPlainText().split(QChar::fromAscii('\n')) ) { 118 line = line.trimmed( ); 119 if( line.isEmpty( ) ) 120 ++tier; 121 else { 122 tr_tracker_info tmp; 123 tmp.announce = tr_strdup( line.toUtf8().constData( ) ); 124 tmp.tier = tier; 125 trackers.append( tmp ); 126 } 127 } 128 129 // pop up the dialog 130 QDialog * dialog = new QDialog( this ); 131 dialog->setWindowTitle( tr( "New Torrent" ) ); 132 myNewDialog = dialog; 133 QVBoxLayout * top = new QVBoxLayout( dialog ); 134 top->addWidget(( myNewLabel = new QLabel)); 135 top->addWidget(( myNewProgress = new QProgressBar )); 136 QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Ok 137 | QDialogButtonBox::Open 138 | QDialogButtonBox::Abort ); 139 myNewButtonBox = buttons; 140 connect( buttons, SIGNAL(clicked(QAbstractButton*)), 141 this, SLOT(onNewButtonBoxClicked(QAbstractButton*)) ); 142 top->addWidget( buttons ); 143 onProgress( ); 144 dialog->show( ); 145 connect( dialog, SIGNAL(destroyed(QObject*)), 146 this, SLOT(onNewDialogDestroyed(QObject*)) ); 147 myTimer.start( 100 ); 148 149 // the file to create 150 const QString path = QString::fromLocal8Bit( myBuilder->top ); 151 const QString torrentName = QFileInfo(path).completeBaseName() + QString::fromAscii(".torrent"); 152 myTarget = QDir( myDestination ).filePath( torrentName ); 153 std::cerr << qPrintable(myTarget) << std::endl; 154 155 // comment 156 QString comment; 157 if( myCommentCheck->isChecked() ) 158 comment = myCommentEdit->text(); 159 160 // start making the torrent 161 tr_makeMetaInfo( myBuilder, 162 myTarget.toUtf8().constData(), 163 (trackers.isEmpty() ? 0 : &trackers.front()), 164 trackers.size(), 165 (comment.isEmpty() ? NULL : comment.toUtf8().constData()), 166 myPrivateCheck->isChecked() ); 167} 168 169/*** 170**** 171***/ 172 173void 174MakeDialog :: onFileClicked( ) 175{ 176 QFileDialog * d = new QFileDialog( this, tr( "Select File" ) ); 177 d->setFileMode( QFileDialog::ExistingFile ); 178 d->setAttribute( Qt::WA_DeleteOnClose ); 179 connect( d, SIGNAL(filesSelected(const QStringList&)), 180 this, SLOT(onFileSelected(const QStringList&)) ); 181 d->show( ); 182} 183void 184MakeDialog :: onFileSelected( const QStringList& list ) 185{ 186 if( !list.empty( ) ) 187 onFileSelected( list.front( ) ); 188} 189void 190MakeDialog :: onFileSelected( const QString& filename ) 191{ 192 myFile = filename; 193 myFileButton->setText( QFileInfo(myFile).fileName() ); 194 onSourceChanged( ); 195} 196 197void 198MakeDialog :: onFolderClicked( ) 199{ 200 QFileDialog * d = new QFileDialog( this, tr( "Select Folder" ) ); 201 d->setFileMode( QFileDialog::Directory ); 202 d->setOption( QFileDialog::ShowDirsOnly ); 203 d->setAttribute( Qt::WA_DeleteOnClose ); 204 connect( d, SIGNAL(filesSelected(const QStringList&)), 205 this, SLOT(onFolderSelected(const QStringList&)) ); 206 d->show( ); 207} 208void 209MakeDialog :: onFolderSelected( const QStringList& list ) 210{ 211 if( !list.empty( ) ) 212 onFolderSelected( list.front( ) ); 213} 214void 215MakeDialog :: onFolderSelected( const QString& filename ) 216{ 217 myFolder = filename; 218 myFolderButton->setText( QFileInfo(myFolder).fileName() ); 219 onSourceChanged( ); 220} 221 222void 223MakeDialog :: onDestinationClicked( ) 224{ 225 QFileDialog * d = new QFileDialog( this, tr( "Select Folder" ) ); 226 d->setFileMode( QFileDialog::Directory ); 227 d->setOption( QFileDialog::ShowDirsOnly ); 228 d->setAttribute( Qt::WA_DeleteOnClose ); 229 connect( d, SIGNAL(filesSelected(const QStringList&)), 230 this, SLOT(onDestinationSelected(const QStringList&)) ); 231 d->show( ); 232} 233void 234MakeDialog :: onDestinationSelected( const QStringList& list ) 235{ 236 if( !list.empty( ) ) 237 onDestinationSelected( list.front() ); 238} 239void 240MakeDialog :: onDestinationSelected( const QString& filename ) 241{ 242 myDestination = filename; 243 myDestinationButton->setText( QFileInfo(myDestination).fileName() ); 244} 245 246void 247MakeDialog :: enableBuddyWhenChecked( QRadioButton * box, QWidget * buddy ) 248{ 249 connect( box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)) ); 250 buddy->setEnabled( box->isChecked( ) ); 251} 252void 253MakeDialog :: enableBuddyWhenChecked( QCheckBox * box, QWidget * buddy ) 254{ 255 connect( box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)) ); 256 buddy->setEnabled( box->isChecked( ) ); 257} 258 259QString 260MakeDialog :: getSource( ) const 261{ 262 return myFileRadio->isChecked( ) ? myFile : myFolder; 263} 264 265void 266MakeDialog :: onButtonBoxClicked( QAbstractButton * button ) 267{ 268 switch( myButtonBox->standardButton( button ) ) 269 { 270 case QDialogButtonBox::Ok: 271 makeTorrent( ); 272 break; 273 274 default: // QDialogButtonBox::Close: 275 deleteLater( ); 276 break; 277 } 278} 279 280/*** 281**** 282***/ 283 284void 285MakeDialog :: onSourceChanged( ) 286{ 287 if( myBuilder ) 288 { 289 tr_metaInfoBuilderFree( myBuilder ); 290 myBuilder = 0; 291 } 292 293 const QString filename = getSource( ); 294 if( !filename.isEmpty( ) ) 295 myBuilder = tr_metaInfoBuilderCreate( filename.toUtf8().constData() ); 296 297 QString text; 298 if( !myBuilder ) 299 text = tr( "<i>No source selected<i>" ); 300 else { 301 QString files = tr( "%Ln File(s)", 0, myBuilder->fileCount ); 302 QString pieces = tr( "%Ln Piece(s)", 0, myBuilder->pieceCount ); 303 text = tr( "%1 in %2; %3 @ %4" ) 304 .arg( Formatter::sizeToString( myBuilder->totalSize ) ) 305 .arg( files ) 306 .arg( pieces ) 307 .arg( Formatter::sizeToString( myBuilder->pieceSize ) ); 308 } 309 310 mySourceLabel->setText( text ); 311} 312 313 314// bah, there doesn't seem to be any cleaner way to override 315// QPlainTextEdit's default desire to be 12 lines tall 316class ShortPlainTextEdit: public QPlainTextEdit { 317 public: 318 virtual ~ShortPlainTextEdit( ) { } 319 ShortPlainTextEdit( QWidget * parent = 0 ): QPlainTextEdit(parent) { } 320 virtual QSize sizeHint ( ) const { return QSize( 256, 50 ); } 321}; 322 323MakeDialog :: MakeDialog( Session & session, QWidget * parent ): 324 QDialog( parent, Qt::Dialog ), 325 mySession( session ), 326 myBuilder( 0 ) 327{ 328 setAcceptDrops( true ); 329 330 connect( &myTimer, SIGNAL(timeout()), this, SLOT(onProgress()) ); 331 332 setWindowTitle( tr( "New Torrent" ) ); 333 QVBoxLayout * top = new QVBoxLayout( this ); 334 top->setSpacing( HIG :: PAD ); 335 336 HIG * hig = new HIG; 337 hig->setContentsMargins( 0, 0, 0, 0 ); 338 hig->addSectionTitle( tr( "Files" ) ); 339 340 QFileIconProvider iconProvider; 341 const int iconSize( style()->pixelMetric( QStyle::PM_SmallIconSize ) ); 342 const QIcon folderIcon = iconProvider.icon( QFileIconProvider::Folder ); 343 const QPixmap folderPixmap = folderIcon.pixmap( iconSize ); 344 QPushButton * b = new QPushButton; 345 b->setIcon( folderPixmap ); 346 b->setStyleSheet( QString::fromAscii( "text-align: left; padding-left: 5; padding-right: 5" ) ); 347 myDestination = QDir::homePath(); 348 b->setText( myDestination ); 349 connect( b, SIGNAL(clicked(bool)), 350 this, SLOT(onDestinationClicked(void)) ); 351 myDestinationButton = b; 352 hig->addRow( tr( "Sa&ve to:" ), b ); 353 354 myFolderRadio = new QRadioButton( tr( "Source F&older:" ) ); 355 connect( myFolderRadio, SIGNAL(toggled(bool)), 356 this, SLOT(onSourceChanged()) ); 357 myFolderButton = new QPushButton; 358 myFolderButton->setIcon( folderPixmap ); 359 myFolderButton->setText( tr( "(None)" ) ); 360 myFolderButton->setStyleSheet( QString::fromAscii( "text-align: left; padding-left: 5; padding-right: 5" ) ); 361 connect( myFolderButton, SIGNAL(clicked(bool)), 362 this, SLOT(onFolderClicked(void)) ); 363 hig->addRow( myFolderRadio, myFolderButton ); 364 enableBuddyWhenChecked( myFolderRadio, myFolderButton ); 365 366 const QIcon fileIcon = iconProvider.icon( QFileIconProvider::File ); 367 const QPixmap filePixmap = fileIcon.pixmap( iconSize ); 368 myFileRadio = new QRadioButton( tr( "Source &File:" ) ); 369 myFileRadio->setChecked( true ); 370 connect( myFileRadio, SIGNAL(toggled(bool)), 371 this, SLOT(onSourceChanged()) ); 372 myFileButton = new QPushButton; 373 myFileButton->setText( tr( "(None)" ) ); 374 myFileButton->setIcon( filePixmap ); 375 myFileButton->setStyleSheet( QString::fromAscii( "text-align: left; padding-left: 5; padding-right: 5" ) ); 376 connect( myFileButton, SIGNAL(clicked(bool)), 377 this, SLOT(onFileClicked(void)) ); 378 hig->addRow( myFileRadio, myFileButton ); 379 enableBuddyWhenChecked( myFileRadio, myFileButton ); 380 381 mySourceLabel = new QLabel( this ); 382 hig->addRow( tr( "" ), mySourceLabel ); 383 384 hig->addSectionDivider( ); 385 hig->addSectionTitle( tr( "Properties" ) ); 386 387 hig->addWideControl( myTrackerEdit = new ShortPlainTextEdit ); 388 const int height = fontMetrics().size( 0, QString::fromAscii("\n\n\n\n") ).height( ); 389 myTrackerEdit->setMinimumHeight( height ); 390 hig->addTallRow( tr( "&Trackers:" ), myTrackerEdit ); 391 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." ) ); 392 l->setAlignment( Qt::AlignLeft ); 393 hig->addRow( tr( "" ), l ); 394 myTrackerEdit->resize( 500, height ); 395 396 myCommentCheck = new QCheckBox( tr( "Co&mment" ) ); 397 myCommentEdit = new QLineEdit( ); 398 hig->addRow( myCommentCheck, myCommentEdit ); 399 enableBuddyWhenChecked( myCommentCheck, myCommentEdit ); 400 401 myPrivateCheck = hig->addWideCheckBox( tr( "&Private torrent" ), false ); 402 403 hig->finish( ); 404 top->addWidget( hig, 1 ); 405 406 myButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok 407 | QDialogButtonBox::Close ); 408 connect( myButtonBox, SIGNAL(clicked(QAbstractButton*)), 409 this, SLOT(onButtonBoxClicked(QAbstractButton*)) ); 410 411 top->addWidget( myButtonBox ); 412 onSourceChanged( ); 413} 414 415MakeDialog :: ~MakeDialog( ) 416{ 417 if( myBuilder ) 418 tr_metaInfoBuilderFree( myBuilder ); 419} 420 421/*** 422**** 423***/ 424 425void 426MakeDialog :: dragEnterEvent( QDragEnterEvent * event ) 427{ 428 const QMimeData * mime = event->mimeData( ); 429 430 if( mime->urls().size() && QFile(mime->urls().front().path()).exists( ) ) 431 event->acceptProposedAction(); 432} 433 434void 435MakeDialog :: dropEvent( QDropEvent * event ) 436{ 437 const QString filename = event->mimeData()->urls().front().path(); 438 const QFileInfo fileInfo( filename ); 439 440 if( fileInfo.exists( ) ) 441 { 442 if( fileInfo.isDir( ) ) 443 { 444 myFolderRadio->setChecked( true ); 445 onFolderSelected( filename ); 446 } 447 else // it's a file 448 { 449 myFileRadio->setChecked( true ); 450 onFileSelected( filename ); 451 } 452 } 453} 454