1
/************************************************************************************************
3
* AccountsDialog.cc - Multi-account form for QTM
5
* Copyright (C) 2007, 2008, Matthew J Smith
7
* This file is part of QTM.
8
* QTM program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License (version 2), as
10
* published by the Free Software Foundation.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
*************************************************************************************************/
26
#include <QListWidget>
27
#include <QListWidgetItem>
30
#include <QStringList>
33
#include <QMessageBox>
34
#include <QModelIndex>
39
#include <QHttpRequestHeader>
40
#include <QHttpResponseHeader>
41
#include <QFocusEvent>
42
#if QT_VERSION <= 0x040300
43
#include <QTextDocument>
48
#include "AccountsDialog.h"
49
#include "ui_AccountsForm.h"
52
AccountsDialog::AccountsDialog( QList<AccountsDialog::Account> &acctList,
58
networkBiz = NoBusiness;
62
leBlogURI->installEventFilter( this );
63
leLocation->installEventFilter( this );
64
pbNew->setDefault( false );
66
// Set up internal account lists; one for the current contents of the accounts,
68
accountList = acctList;
69
originalAccountList = acctList;
71
hboxLayout->setStretchFactor( hboxLayout->itemAt( 0 )->layout(), 2 );
72
hboxLayout->setStretchFactor( hboxLayout->itemAt( 1 )->layout(), 3 );
74
for( int i = 0; i < accountList.count(); i++ ) {
75
a = accountList.at( i ).name;
77
lwAccountList->addItem( tr( "(No name)" ) );
79
lwAccountList->addItem( a );
81
//qDebug( "added the templates" );
83
// Set up list of account widgets;
84
accountWidgets << leName << cbHostedBlogType << leServer << leLocation
85
<< lePort << leLogin << lePassword
86
<< chCategoriesEnabled << chPostDateTime << chAllowComments << chAllowTB << chUseWordpressAPI;
88
pbReset->setVisible( false );
90
Q_FOREACH( QWidget *w, accountWidgets )
91
w->setEnabled( false );
93
doingNewAccount = false;
97
addAccount = new QAction( tr( "&Add account" ), lwAccountList );
98
removeAccount = new QAction( tr( "&Remove this account" ), lwAccountList );
99
lwAccountList->addAction( addAccount );
100
lwAccountList->addAction( removeAccount );
101
lwAccountList->setContextMenuPolicy( Qt::ActionsContextMenu );
103
connect( addAccount, SIGNAL( triggered( bool ) ),
104
this, SLOT( doNewAccount() ) );
105
connect( removeAccount, SIGNAL( triggered( bool ) ),
106
this, SLOT( removeThisAccount() ) );
107
connect( lwAccountList, SIGNAL( currentRowChanged( int ) ),
108
this, SLOT( changeListIndex( int ) ) );
110
entryDateTime = QDateTime();
111
currentRow = acctIndex;
112
lwAccountList->setCurrentRow( acctIndex );
115
void AccountsDialog::changeListIndex( int index )
119
if( doingNewAccount ) {
121
lwAccountList->item( (lwAccountList->count())-1 )->setText( leName->text().isEmpty() ?
125
accountList.removeAt( currentRow );
126
lwAccountList->takeItem( lwAccountList->count()-1 );
130
doingNewAccount = false;
133
leName->setText( accountList[currentRow].name );
134
leServer->setText( accountList[currentRow].server );
135
leLocation->setText( accountList[currentRow].location );
136
lePort->setText( accountList[currentRow].port );
137
leLogin->setText( accountList[currentRow].login );
138
lePassword->setText( accountList[currentRow].password );
140
if( accountList[currentRow].hostedBlogType != -1 )
141
cbHostedBlogType->setCurrentIndex( accountList[currentRow].hostedBlogType );
143
chCategoriesEnabled->setCheckState( accountList[currentRow].categoriesEnabled ?
144
Qt::Checked : Qt::Unchecked );
145
chPostDateTime->setCheckState( accountList[currentRow].postDateTime ?
146
Qt::Checked : Qt::Unchecked );
147
chAllowComments->setCheckState( accountList[currentRow].allowComments ? Qt::Checked :
149
chAllowTB->setCheckState( accountList[currentRow].allowTB ? Qt::Checked : Qt::Unchecked );
150
chUseWordpressAPI->setCheckState( accountList[currentRow].useWordpressAPI ?
151
Qt::Checked : Qt::Unchecked );
153
Q_FOREACH( QWidget *w, accountWidgets )
154
w->setEnabled( true );
157
void AccountsDialog::doNewAccount()
161
entryDateTime = QDateTime::currentDateTime();
162
Q_FOREACH( QWidget *w, accountWidgets )
163
w->setEnabled( true );
165
accountList.append( Account() );
167
lwAccountList->disconnect( SIGNAL( currentRowChanged( int ) ),
168
this, SLOT( changeListIndex( int ) ) );
170
Q_FOREACH( QWidget *v, accountWidgets ) {
171
le = qobject_cast<QLineEdit *>( v );
176
lwAccountList->addItem( tr( "New account" ) );
177
currentRow = lwAccountList->count()-1;
178
lwAccountList->setCurrentRow( lwAccountList->count()-1 );
179
connect( lwAccountList, SIGNAL( currentRowChanged( int ) ),
180
this, SLOT( changeListIndex( int ) ) );
182
doingNewAccount = true;
183
leName->setFocus( Qt::OtherFocusReason );
184
connect( qApp, SIGNAL( focusChanged( QWidget *, QWidget * ) ),
185
this, SLOT( assignSlug() ) );
190
void AccountsDialog::removeThisAccount()
194
int c = lwAccountList->currentRow();
196
if( lwAccountList->count() == 1 ) {
197
lwAccountList->disconnect( SIGNAL( currentRowChanged( int ) ) );
198
lwAccountList->clear();
201
Q_FOREACH( QWidget *w, accountWidgets ) {
202
le = qobject_cast<QLineEdit *>( w );
205
le->setEnabled( false );
207
ch = qobject_cast<QCheckBox *>( w );
209
ch->setChecked( false );
210
ch->setEnabled( false );
215
lwAccountList->takeItem( c );
216
accountList.removeAt( c );
220
void AccountsDialog::setDirty()
223
Q_FOREACH( QWidget *w, accountWidgets )
224
disconnect( w, 0, this, SLOT( setDirty() ) );
227
void AccountsDialog::setClean()
231
Q_FOREACH( QWidget *w, accountWidgets )
232
disconnect( w, 0, this, SLOT( setDirty() ) );
234
Q_FOREACH( QWidget *v, accountWidgets ) {
235
if( qobject_cast<QLineEdit *>( v ) )
236
connect( v, SIGNAL( textChanged( const QString & ) ),
237
this, SLOT( setDirty() ) );
241
void AccountsDialog::acceptAccount()
244
if( doingNewAccount ) {
245
accountList.append( currentAcct );
247
if( lwAccountList->count() == 1 )
248
connect( lwAccountList, SIGNAL( currentRowChanged( int ) ),
249
this, SLOT( changeListIndex( int ) ) );
251
if( lwAccountList->count() ) {
252
lwAccountList->item( currentRow )->setText( leName->text().isEmpty() ?
253
tr( "(No name)" ) : leName->text() );
257
doingNewAccount = false;
261
/* Private slot method to assign a unique slug name to identify an account.
262
* It's done by the second, which should ensure uniqueness as long as identical
263
* accounts aren't created by a program.
265
void AccountsDialog::assignSlug()
267
if( accountList.count() != 0 ) {
268
currentAccountId = QString( "%1_%2" ).arg( entryDateTime.toString( Qt::ISODate ) )
269
.arg( leName->text().toLower()
270
.replace( QRegExp( "\\s" ), "_" ) );
271
accountList[currentRow].id = currentAccountId;
275
void AccountsDialog::on_pbWhatsThis_clicked()
277
QWhatsThis::enterWhatsThisMode();
280
void AccountsDialog::on_pbOK_clicked()
282
if( doingNewAccount && !dirty )
283
accountList.removeAt( currentRow );
288
void AccountsDialog::on_leBlogURI_returnPressed()
291
QString uriServer, urisLocation;
292
QRegExp re( "/.*\\.[shtml|dhtml|phtml|html|htm|php|cgi|pl|py]$" );
294
QUrl uri = QUrl( leBlogURI->text(), QUrl::TolerantMode );
295
QString uris = uri.toString();
297
if( !uri.isValid() ) {
298
QMessageBox::information( 0, tr( "QTM: URI not valid" ),
299
tr( "That web location is not valid." ),
300
QMessageBox::Cancel );
304
//bool found = false;
305
// QStringList hostedAccountStrings, hostedAccountServers, hostedAccountLocations;
306
QStringList wpmuHosts;
307
wpmuHosts << "wordpress.com" << "blogsome.com" << "blogs.ie"
308
<< "hadithuna.com" << "edublogs.com" << "weblogs.us"
309
<< "blogvis.com" << "blogates.com";
310
/* hostedAccountEndpoints << "@host@;xmlrpc.php"
311
<< "www.typepad.com;/t/api"
312
<< "www.squarespace.com;/do/process/external/PostInterceptor"
313
<< "@host@;xmlrpc.php"
314
<< "@host@;xmlrpc.php"
315
<< "@host@;xmlrpc.php"; */
317
// First check for TypePad and SquareSpace
318
if( uris.contains( "squarespace.com" ) ) {
319
leServer->setText( "www.squarespace.com" );
320
accountList[currentRow].server = "www.squarespace.com";
321
leLocation->setText( "/do/process/external/PostInterceptor" );
322
accountList[currentRow].location = "/do/process/external/PostInterceptor";
326
if( uris.contains( "typepad.com" ) ) {
327
leServer->setText( "www.typepad.com" );
328
accountList[currentRow].server = "www.typepad.com";
329
leLocation->setText( "/t/api" );
330
accountList[currentRow].location = "/t/api";
334
// Now check if it's a Wordpress MU host
335
for( i = 0; i <= wpmuHosts.count(); i++ ) {
336
if( i < wpmuHosts.count() ) {
337
if( uris.contains( wpmuHosts.at( i ) ) ) {
338
leServer->setText( uri.host() );
339
accountList[currentRow].server = uri.host();
340
leLocation->setText( "/xmlrpc.php" );
341
accountList[currentRow].location = "/xmlrpc.php";
343
accountList[currentRow].port = "";
344
leLogin->setFocus( Qt::TabFocusReason );
350
// Is this a self-hosted Wordpress, Textpattern or Drupal site?
351
if( cbHostedBlogType->currentIndex() >= 5 &&
352
cbHostedBlogType->currentIndex() <= 7 ) {
354
if( cbHostedBlogType->currentIndex() == 7 ) // i.e. Textpattern
355
endpoint = "rpc/index.php";
357
endpoint = "xmlrpc.php";
359
accountList[currentRow].server = uris.section( "//", 1, 1 ).section( "/", 0, 0 );
360
leServer->setText( accountList[currentRow].server );
362
urisLocation = uri.path();
363
if( re.exactMatch( urisLocation ) )
364
urisLocation = urisLocation.section( "/", 0, -2 ).append( endpoint );
366
urisLocation.append( urisLocation.endsWith( '/' ) ? endpoint :
367
QString( "/%1" ).arg( endpoint ) );
368
leLocation->setText( urisLocation );
369
accountList[currentRow].location = urisLocation;
373
// Now test for an rsd.xml file
374
http->setHost( uri.host() );
375
QString loc( uri.path() );
376
if( re.exactMatch( loc ) )
377
http->get( loc.section( "/", -2, 0, QString::SectionIncludeTrailingSep )
378
.append( "rsd.xml" ) );
380
http->get( loc.append( loc.endsWith( '/' ) ? "rsd.xml" : "/rsd.xml" ) );
382
if( qApp->overrideCursor() == 0 )
383
qApp->setOverrideCursor( QCursor( Qt::BusyCursor ) );
385
networkBiz = FindingRsdXml;
387
connect( http, SIGNAL( requestFinished( int, bool ) ),
388
this, SLOT( handleRequestFinished( int, bool ) ) );
389
connect( http, SIGNAL( done( bool ) ),
390
this, SLOT( handleHttpDone( bool ) ) );
394
void AccountsDialog::handleRequestFinished( int /* id */,
398
httpByteArray = http->readAll();
401
void AccountsDialog::handleHttpDone( bool error )
406
QHttpResponseHeader responseHeader;
409
QString failure = tr( "QTM failed to auto-configure access to your account. "
410
"Please consult the documentation for your conent management system "
413
responseHeader = http->lastResponse();
416
switch( networkBiz ) {
418
if( responseHeader.statusCode() == 200 ) { /* 200 means success */
419
rsdXml.setContent( httpByteArray );
420
httpByteArray = QByteArray();
421
if( rsdXml.documentElement().tagName() == "rsd" ) {
422
apis = rsdXml.elementsByTagName( "api" );
423
for( i = 0; i < apis.count(); i++ ) {
424
if( apis.at( i ).toElement().attribute( "name" ) == "MetaWeblog" ) {
425
url = QUrl( apis.at( i ).toElement().attribute( "apiLink" ) );
426
if( url.isValid() ) {
427
leServer->setText( url.host() );
428
accountList[currentRow].server = url.host();
429
leLocation->setText( url.path() );
430
accountList[currentRow].location = url.path();
438
QMessageBox::information( this, tr( "QTM: Failure" ), failure, QMessageBox::Cancel );
441
// Attempt to find tell-tale files has failed
442
QMessageBox::information( this, tr( "QTM: Failure" ), failure, QMessageBox::Cancel );
444
currentReq = QHttpRequestHeader();
445
disconnect( http, SIGNAL( requestFinished( int, bool ) ), this, 0 );
446
disconnect( http, SIGNAL( done( bool ) ), this, 0 );
448
case FindingXmlrpcPhp:
450
// If it finds xmlrpc.php, it returns a short string with a successful (200) status code
451
if( responseHeader.statusCode() == 200 ) {
452
leServer->setText( currentHost );
453
currentHost = QString();
454
leLocation->setText( QUrl( currentReq.path() ).path() );
456
currentReq = QHttpRequestHeader();
457
networkBiz = NoBusiness;
458
disconnect( http, SIGNAL( requestFinished() ), this, 0 );
459
disconnect( http, SIGNAL( done( bool ) ), this, 0 );
464
http->setHost( QUrl( currentReq.path() ).host() );
465
http->get( QUrl( currentReq.path() ).path().replace( "xmlrpc.php", "rsd.xml" ) );
466
currentReq = QHttpRequestHeader();
467
networkBiz = FindingRsdXml;
473
QMessageBox::information( this, tr( "QTM - Network failure" ),
474
tr( "QTM could not contact the site. Please consult the documentation "
475
"for your content management system or service and enter the "
476
"server and location manually." ),
477
QMessageBox::Cancel );
479
currentReq = QHttpRequestHeader();
481
if( qApp->overrideCursor() != 0 )
482
qApp->restoreOverrideCursor();
485
void AccountsDialog::on_leName_textEdited( const QString &newName )
487
if( currentRow != -1 ) {
488
// qDebug( "title edited" );
489
lwAccountList->item( currentRow )->setText( newName.isEmpty() ?
490
tr( "(No name)" ) : newName );
491
accountList[currentRow].name = newName;
495
void AccountsDialog::on_leServer_textEdited( const QString &newServ )
497
if( currentRow != -1 )
498
accountList[currentRow].server = newServ;
501
void AccountsDialog::on_leLocation_textEdited( const QString &text )
503
if( currentRow != -1 )
504
accountList[currentRow].location = text;
507
void AccountsDialog::on_lePort_textEdited( const QString &text )
509
if( currentRow != -1 )
510
accountList[currentRow].port = text;
513
void AccountsDialog::on_leLogin_textEdited( const QString &text )
515
if( currentRow != -1 )
516
accountList[currentRow].login = text;
519
void AccountsDialog::on_lePassword_textEdited( const QString &text )
521
if( currentRow != -1 )
522
accountList[currentRow].password = text;
525
void AccountsDialog::on_chCategoriesEnabled_clicked( bool )
527
if( currentRow != -1 )
528
accountList[currentRow].categoriesEnabled = chCategoriesEnabled->isChecked();
531
void AccountsDialog::on_chPostDateTime_clicked( bool )
533
if( currentRow != -1 )
534
accountList[currentRow].postDateTime = chPostDateTime->isChecked();
537
void AccountsDialog::on_chAllowComments_clicked( bool )
539
if( currentRow != -1 )
540
accountList[currentRow].allowComments = chAllowComments->isChecked();
543
void AccountsDialog::on_chAllowTB_clicked( bool )
545
if( currentRow != -1 )
546
accountList[currentRow].allowTB = chAllowTB->isChecked();
549
void AccountsDialog::on_chUseWordpressAPI_clicked( bool )
551
if( currentRow != -1 )
552
accountList[currentRow].useWordpressAPI = chUseWordpressAPI->isChecked();
555
void AccountsDialog::on_cbHostedBlogType_activated( int newIndex )
558
case 1: // wordpress.com
559
leServer->setText( "yourblog.wordpress.com" );
560
leLocation->setText( "/xmlrpc.php" );
563
leServer->setText( "www.typepad.com" );
564
leLocation->setText( "/t/api" );
566
case 3: // SquareSpace
567
leServer->setText( "www.squarespace.com" );
568
leLocation->setText( "/do/process/external/PostInterceptor" );
570
case 4: // Movable type
571
case 5: // Self-hosted Wordpress
576
case 7: // TextPattern
577
chAllowTB->setChecked( false ); // these platforms don't support it
582
accountList[currentRow].hostedBlogType = newIndex;
585
bool AccountsDialog::eventFilter( QObject *obj, QEvent *event )
587
// When the blog URI is typed in and Tab is pressed, the same must happen as when
588
// Return is pressed, i.e. QTM must try to find the endpoint.
589
if( obj == leBlogURI ) {
590
switch( event->type() ) {
591
case QEvent::FocusOut:
592
if( !leBlogURI->text().isEmpty() ) {
593
on_leBlogURI_returnPressed();
596
return QObject::eventFilter( obj, event );
600
// The text in the location field must start with /; otherwise, the web server will
602
if( obj == leLocation ) {
603
switch( event->type() ) {
604
case QEvent::FocusOut:
605
if( !leLocation->text().isEmpty() && !leLocation->text().startsWith( '/' ) ) {
606
QString newLocation = leLocation->text().prepend( '/' );
607
leLocation->setText( newLocation );
608
if( currentRow != -1 )
609
accountList[currentRow].location = newLocation;
612
return QObject::eventFilter( obj, event );
616
return QObject::eventFilter( obj, event );