/* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browserwidget.h" #include "collectionattributespage.h" #include "collectioninternalspage.h" #include "collectionaclpage.h" #include "dbaccess.h" #include "akonadibrowsermodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAttributePageFactory, CollectionAttributePage) AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionInternalsPageFactory, CollectionInternalsPage) AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAclPageFactory, CollectionAclPage) Q_DECLARE_METATYPE( QSet ) BrowserWidget::BrowserWidget(KXmlGuiWindow *xmlGuiWindow, QWidget * parent) : QWidget( parent ), mAttrModel( 0 ), mStdActionManager( 0 ), mMonitor( 0 ), mCacheOnlyAction( 0 ) { Q_ASSERT( xmlGuiWindow ); QVBoxLayout *layout = new QVBoxLayout( this ); QSplitter *splitter = new QSplitter( Qt::Horizontal, this ); splitter->setObjectName( "collectionSplitter" ); layout->addWidget( splitter ); QSplitter *splitter2 = new QSplitter( Qt::Vertical, this ); splitter2->setObjectName( "ffvSplitter" ); mCollectionView = new Akonadi::EntityTreeView( xmlGuiWindow, this ); mCollectionView->setObjectName( "CollectionView" ); mCollectionView->setSelectionMode( QAbstractItemView::ExtendedSelection ); splitter2->addWidget( mCollectionView ); EntityListView *favoritesView = new EntityListView( xmlGuiWindow, this ); //favoritesView->setViewMode( QListView::IconMode ); splitter2->addWidget( favoritesView ); splitter->addWidget( splitter2 ); ChangeRecorder *tagRecorder = new ChangeRecorder( this ); tagRecorder->setTypeMonitored( Monitor::Tags ); tagRecorder->setChangeRecordingEnabled( false ); QTreeView *tagView = new QTreeView( this ); TagModel *tagModel = new Akonadi::TagModel( tagRecorder, this ); tagView->setModel( tagModel ); splitter2->addWidget( tagView ); Session *session = new Session( "AkonadiConsole Browser Widget", this ); // monitor collection changes mBrowserMonitor = new ChangeRecorder( this ); mBrowserMonitor->setSession(session); mBrowserMonitor->setCollectionMonitored( Collection::root() ); mBrowserMonitor->fetchCollection( true ); mBrowserMonitor->setAllMonitored( true ); // TODO: Only fetch the envelope etc if possible. mBrowserMonitor->itemFetchScope().fetchFullPayload( true ); mBrowserMonitor->itemFetchScope().setCacheOnly( true ); mBrowserModel = new AkonadiBrowserModel( mBrowserMonitor, this ); mBrowserModel->setItemPopulationStrategy( EntityTreeModel::LazyPopulation ); mBrowserModel->setShowSystemEntities( true ); // new ModelTest( mBrowserModel ); EntityMimeTypeFilterModel *collectionFilter = new EntityMimeTypeFilterModel( this ); collectionFilter->setSourceModel( mBrowserModel ); collectionFilter->addMimeTypeInclusionFilter( Collection::mimeType() ); collectionFilter->setHeaderGroup( EntityTreeModel::CollectionTreeHeaders ); collectionFilter->setDynamicSortFilter( true ); collectionFilter->setSortCaseSensitivity( Qt::CaseInsensitive ); statisticsProxyModel = new KPIM::StatisticsProxyModel( this ); statisticsProxyModel->setToolTipEnabled( true ); statisticsProxyModel->setSourceModel( collectionFilter ); QuotaColorProxyModel *quotaProxyModel = new QuotaColorProxyModel( this ); quotaProxyModel->setWarningThreshold( 50.0 ); quotaProxyModel->setSourceModel( statisticsProxyModel ); mCollectionView->setModel( quotaProxyModel ); Akonadi::SelectionProxyModel *selectionProxyModel = new Akonadi::SelectionProxyModel( mCollectionView->selectionModel(), this ); selectionProxyModel->setSourceModel( mBrowserModel ); selectionProxyModel->setFilterBehavior( KSelectionProxyModel::ChildrenOfExactSelection ); EntityMimeTypeFilterModel *itemFilter = new EntityMimeTypeFilterModel( this ); itemFilter->setSourceModel( selectionProxyModel ); itemFilter->addMimeTypeExclusionFilter( Collection::mimeType() ); itemFilter->setHeaderGroup( EntityTreeModel::ItemListHeaders ); connect( mBrowserModel, SIGNAL(columnsChanged()), itemFilter, SLOT(invalidate()) ); AkonadiBrowserSortModel *sortModel = new AkonadiBrowserSortModel( mBrowserModel, this ); sortModel->setDynamicSortFilter( true ); sortModel->setSourceModel( itemFilter ); const KConfigGroup group = KGlobal::config()->group( "FavoriteCollectionsModel" ); FavoriteCollectionsModel *favoritesModel = new FavoriteCollectionsModel( mBrowserModel, group, this ); favoritesView->setModel( favoritesModel ); QSplitter *splitter3 = new QSplitter( Qt::Vertical, this ); splitter3->setObjectName( "itemSplitter" ); splitter->addWidget( splitter3 ); QWidget *itemViewParent = new QWidget( this ); itemUi.setupUi( itemViewParent ); itemUi.modelBox->addItem( "Generic" ); itemUi.modelBox->addItem( "Mail" ); itemUi.modelBox->addItem( "Contacts" ); itemUi.modelBox->addItem( "Calendar/Tasks" ); connect( itemUi.modelBox, SIGNAL(activated(int)), SLOT(modelChanged()) ); QTimer::singleShot( 0, this, SLOT(modelChanged()) ); itemUi.itemView->setXmlGuiClient( xmlGuiWindow ); itemUi.itemView->setModel( sortModel ); itemUi.itemView->setSelectionMode( QAbstractItemView::ExtendedSelection ); connect( itemUi.itemView, SIGNAL(activated(QModelIndex)), SLOT(itemActivated(QModelIndex)) ); connect( itemUi.itemView, SIGNAL(clicked(QModelIndex)), SLOT(itemActivated(QModelIndex)) ); splitter3->addWidget( itemViewParent ); itemViewParent->layout()->setMargin( 0 ); QWidget *contentViewParent = new QWidget( this ); contentUi.setupUi( contentViewParent ); contentUi.saveButton->setEnabled( false ); connect( contentUi.saveButton, SIGNAL(clicked()), SLOT(save()) ); splitter3->addWidget( contentViewParent ); connect( contentUi.attrAddButton, SIGNAL(clicked()), SLOT(addAttribute()) ); connect( contentUi.attrDeleteButton, SIGNAL(clicked()), SLOT(delAttribute()) ); CollectionPropertiesDialog::registerPage( new CollectionAclPageFactory() ); CollectionPropertiesDialog::registerPage( new CollectionAttributePageFactory() ); CollectionPropertiesDialog::registerPage( new CollectionInternalsPageFactory() ); Control::widgetNeedsAkonadi( this ); mStdActionManager = new StandardActionManager( xmlGuiWindow->actionCollection(), xmlGuiWindow ); mStdActionManager->setCollectionSelectionModel( mCollectionView->selectionModel() ); mStdActionManager->setItemSelectionModel( itemUi.itemView->selectionModel() ); mStdActionManager->setFavoriteCollectionsModel( favoritesModel ); mStdActionManager->setFavoriteSelectionModel( favoritesView->selectionModel() ); mStdActionManager->createAllActions(); mCacheOnlyAction = new KToggleAction( i18n("Cache only retrieval"), xmlGuiWindow ); mCacheOnlyAction->setChecked( true ); xmlGuiWindow->actionCollection()->addAction( "akonadiconsole_cacheonly", mCacheOnlyAction ); connect( mCacheOnlyAction, SIGNAL(toggled(bool)), SLOT(updateItemFetchScope()) ); m_stateMaintainer = new KViewStateMaintainer( KGlobal::config()->group("CollectionViewState"), this ); m_stateMaintainer->setView( mCollectionView ); m_stateMaintainer->restoreState(); } BrowserWidget::~BrowserWidget() { m_stateMaintainer->saveState(); } void BrowserWidget::clear() { contentUi.stack->setCurrentWidget( contentUi.unsupportedTypePage ); contentUi.dataView->clear(); contentUi.id->clear(); contentUi.remoteId->clear(); contentUi.mimeType->clear(); contentUi.revision->clear(); contentUi.size->clear(); contentUi.modificationtime->clear(); contentUi.flags->clear(); contentUi.tags->clear(); contentUi.attrView->setModel( 0 ); } void BrowserWidget::itemActivated(const QModelIndex & index) { const Item item = index.sibling( index.row(), 0 ).data( EntityTreeModel::ItemRole ).value< Item >(); if ( !item.isValid() ) { clear(); return; } ItemFetchJob *job = new ItemFetchJob( item, this ); job->fetchScope().fetchFullPayload(); job->fetchScope().fetchAllAttributes(); job->fetchScope().setFetchTags( true ); connect( job, SIGNAL(result(KJob*)), SLOT(itemFetchDone(KJob*)), Qt::QueuedConnection ); } void BrowserWidget::itemFetchDone(KJob * job) { ItemFetchJob *fetch = static_cast( job ); if ( job->error() ) { kWarning( 5265 ) << "Item fetch failed: " << job->errorString(); } else if ( fetch->items().isEmpty() ) { kWarning( 5265 ) << "No item found!"; } else { const Item item = fetch->items().first(); setItem( item ); } } void BrowserWidget::setItem( const Akonadi::Item &item ) { mCurrentItem = item; if ( item.hasPayload() ) { contentUi.contactView->setItem( item ); contentUi.stack->setCurrentWidget( contentUi.contactViewPage ); } else if ( item.hasPayload() ) { contentUi.contactGroupView->setItem( item ); contentUi.stack->setCurrentWidget( contentUi.contactGroupViewPage ); } else if ( item.hasPayload() ) { contentUi.incidenceView->setItem( item ); contentUi.stack->setCurrentWidget( contentUi.incidenceViewPage ); } else if ( item.mimeType() == "message/rfc822" || item.mimeType() == "message/news" ) { contentUi.mailView->setMessageItem( item, MessageViewer::Viewer::Force ); contentUi.stack->setCurrentWidget( contentUi.mailViewPage ); } else if ( item.hasPayload() ) { contentUi.imageView->setPixmap( item.payload() ); contentUi.stack->setCurrentWidget( contentUi.imageViewPage ); } else { contentUi.stack->setCurrentWidget( contentUi.unsupportedTypePage ); } QByteArray data = item.payloadData(); contentUi.dataView->setPlainText( data ); contentUi.id->setText( QString::number( item.id() ) ); contentUi.remoteId->setText( item.remoteId() ); contentUi.mimeType->setText( item.mimeType() ); contentUi.revision->setText( QString::number( item.revision() ) ); contentUi.size->setText( QString::number( item.size() ) ); contentUi.modificationtime->setText( item.modificationTime().toString() + ( " UTC" ) ); QStringList flags; foreach ( const Item::Flag &f, item.flags() ) flags << QString::fromUtf8( f ); contentUi.flags->setItems( flags ); QStringList tags; foreach ( const Tag &tag, item.tags() ) tags << tag.url().url(); contentUi.tags->setItems( tags ); Attribute::List list = item.attributes(); delete mAttrModel; mAttrModel = new QStandardItemModel( list.count(), 2 ); QStringList labels; labels << i18n( "Attribute" ) << i18n( "Value" ); mAttrModel->setHorizontalHeaderLabels( labels ); for ( int i = 0; i < list.count(); ++i ) { QModelIndex index = mAttrModel->index( i, 0 ); Q_ASSERT( index.isValid() ); mAttrModel->setData( index, QString::fromLatin1( list[i]->type() ) ); index = mAttrModel->index( i, 1 ); Q_ASSERT( index.isValid() ); mAttrModel->setData( index, QString::fromLatin1( list[i]->serialized() ) ); mAttrModel->itemFromIndex( index )->setFlags( Qt::ItemIsEditable | mAttrModel->flags( index ) ); } contentUi.attrView->setModel( mAttrModel ); if ( mMonitor ) mMonitor->deleteLater(); // might be the one calling us mMonitor = new Monitor( this ); mMonitor->setItemMonitored( item ); mMonitor->itemFetchScope().fetchFullPayload(); mMonitor->itemFetchScope().fetchAllAttributes(); qRegisterMetaType >(); connect( mMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), SLOT(setItem(Akonadi::Item)), Qt::QueuedConnection ); } void BrowserWidget::modelChanged() { switch ( itemUi.modelBox->currentIndex() ) { case 1: mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::MailMode); break; case 2: mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::ContactsMode); break; case 3: mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::CalendarMode); break; default: mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::GenericMode); } } void BrowserWidget::save() { Q_ASSERT( mAttrModel ); const QByteArray data = contentUi.dataView->toPlainText().toUtf8(); Item item = mCurrentItem; item.setRemoteId( contentUi.remoteId->text() ); foreach ( const Item::Flag &f, mCurrentItem.flags() ) item.clearFlag( f ); foreach ( const QString &s, contentUi.flags->items() ) item.setFlag( s.toUtf8() ); foreach ( const Tag &tag, mCurrentItem.tags() ) item.clearTag( tag ); foreach ( const QString &s, contentUi.tags->items() ) item.setTag( Tag::fromUrl( s ) ); item.setPayloadFromData( data ); item.clearAttributes(); for ( int i = 0; i < mAttrModel->rowCount(); ++i ) { const QModelIndex typeIndex = mAttrModel->index( i, 0 ); Q_ASSERT( typeIndex.isValid() ); const QModelIndex valueIndex = mAttrModel->index( i, 1 ); Q_ASSERT( valueIndex.isValid() ); Attribute* attr = AttributeFactory::createAttribute( mAttrModel->data( typeIndex ).toString().toLatin1() ); Q_ASSERT( attr ); attr->deserialize( mAttrModel->data( valueIndex ).toString().toLatin1() ); item.addAttribute( attr ); } ItemModifyJob *store = new ItemModifyJob( item, this ); connect( store, SIGNAL(result(KJob*)), SLOT(saveResult(KJob*)) ); } void BrowserWidget::saveResult(KJob * job) { if ( job->error() ) { KMessageBox::error( this, i18n( "Failed to save changes: %1", job->errorString() ) ); } } void BrowserWidget::addAttribute() { if ( !mAttrModel || contentUi.attrName->text().isEmpty() ) return; const int row = mAttrModel->rowCount(); mAttrModel->insertRow( row ); QModelIndex index = mAttrModel->index( row, 0 ); Q_ASSERT( index.isValid() ); mAttrModel->setData( index, contentUi.attrName->text() ); contentUi.attrName->clear(); contentUi.saveButton->setEnabled( true ); } void BrowserWidget::delAttribute() { if ( !mAttrModel ) return; QModelIndexList selection = contentUi.attrView->selectionModel()->selectedRows(); if ( selection.count() != 1 ) return; mAttrModel->removeRow( selection.first().row() ); if ( mAttrModel->rowCount() == 0 ) { contentUi.saveButton->setEnabled( false ); } } void BrowserWidget::dumpToXml() { const Collection root = currentCollection(); if ( !root.isValid() ) return; const QString fileName = KFileDialog::getSaveFileName( KUrl(), "*.xml", this, i18n( "Select XML file" ) ); if ( fileName.isEmpty() ) return; #if 0 // TODO: port me, can't use XmlWriteJob here, it's in runtime, call the akonadi2xml cli tool instead XmlWriteJob *job = new XmlWriteJob( root, fileName, this ); connect( job, SIGNAL(result(KJob*)), SLOT(dumpToXmlResult(KJob*)) ); #endif } void BrowserWidget::dumpToXmlResult( KJob* job ) { if ( job->error() ) KMessageBox::error( this, job->errorString() ); } void BrowserWidget::clearCache() { const Collection coll = currentCollection(); if ( !coll.isValid() ) return; QString str = QString("DELETE FROM PimItemTable WHERE collectionId=%1").arg(coll.id()); qDebug() << str; QSqlQuery query( str, DbAccess::database() ); if (query.exec() ) { if (query.lastError().isValid()) { qDebug() << query.lastError(); KMessageBox::error( this, query.lastError().text() ); } } } Akonadi::Collection BrowserWidget::currentCollection() const { return mCollectionView->currentIndex().data( EntityTreeModel::CollectionRole ).value(); } void BrowserWidget::updateItemFetchScope() { mBrowserMonitor->itemFetchScope().setCacheOnly( mCacheOnlyAction->isChecked() ); }