source: trunk/qt/make-dialog.cc @ 14198

Last change on this file since 14198 was 14198, checked in by jordan, 8 years ago

(trunk, qt) #3606 'Qt client New Torrent from Source Folder is Goofy' -- fixed, mikedld

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