Fix off by one error causing the scrollbar to show up.
[kdepim.git] / akonadiconsole / browserwidget.cpp
blob028dd9c9bdd530641bfe90955727d9c5c63e7455
1 /*
2 Copyright (c) 2007 Volker Krause <vkrause@kde.org>
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
20 #include "browserwidget.h"
22 #include "collectionattributespage.h"
23 #include "collectioninternalspage.h"
24 #include "collectionaclpage.h"
25 #include "settings.h"
26 #include "akonadibrowsermodel.h"
28 #include <akonadi/attributefactory.h>
29 #include <akonadi/changerecorder.h>
30 #include <akonadi/control.h>
31 #include <akonadi/entitymimetypefiltermodel.h>
32 #include <akonadi/item.h>
33 #include <akonadi/itemfetchjob.h>
34 #include <akonadi/itemfetchscope.h>
35 #include <akonadi/itemmodifyjob.h>
36 #include <akonadi/job.h>
37 #include <akonadi/collectionfilterproxymodel.h>
38 #include <akonadi/collectionpropertiesdialog.h>
39 #include <akonadi/selectionproxymodel.h>
40 #include <akonadi/session.h>
41 #include <akonadi/standardactionmanager.h>
42 #include <akonadi/entitylistview.h>
43 #include <akonadi/entitytreeview.h>
44 #include <akonadi/etmviewstatesaver.h>
45 #include <akonadi/favoritecollectionsmodel.h>
46 #include <akonadi_next/quotacolorproxymodel.h>
47 #include <akonadi/statisticsproxymodel.h>
48 #include <akonadi_next/kviewstatemaintainer.h>
50 #include <kabc/addressee.h>
51 #include <kabc/contactgroup.h>
52 #include <kcalcore/incidence.h>
54 #include <kdebug.h>
55 #include <kconfig.h>
56 #include <kconfiggroup.h>
57 #include <kfiledialog.h>
58 #include <kmessagebox.h>
59 #include <kxmlguiwindow.h>
61 #include <nepomuk/resource.h>
62 #include <nepomuk/resourcemanager.h>
63 #include <nepomuk/variant.h>
65 #include <QSplitter>
66 #include <QTextEdit>
67 #include <QVBoxLayout>
68 #include <QStackedWidget>
69 #include <QtGui/QSortFilterProxyModel>
70 #include <QStandardItemModel>
71 #include <QTimer>
73 using namespace Akonadi;
75 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAttributePageFactory, CollectionAttributePage)
76 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionInternalsPageFactory, CollectionInternalsPage)
77 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAclPageFactory, CollectionAclPage)
79 Q_DECLARE_METATYPE( QSet<QByteArray> )
81 BrowserWidget::BrowserWidget(KXmlGuiWindow *xmlGuiWindow, QWidget * parent) :
82 QWidget( parent ),
83 mAttrModel( 0 ),
84 mNepomukModel( 0 ),
85 mStdActionManager( 0 ),
86 mMonitor( 0 )
88 Q_ASSERT( xmlGuiWindow );
89 QVBoxLayout *layout = new QVBoxLayout( this );
91 QSplitter *splitter = new QSplitter( Qt::Horizontal, this );
92 splitter->setObjectName( "collectionSplitter" );
93 layout->addWidget( splitter );
95 QSplitter *splitter2 = new QSplitter( Qt::Vertical, this );
96 splitter2->setObjectName( "ffvSplitter" );
98 mCollectionView = new Akonadi::EntityTreeView( xmlGuiWindow, this );
99 mCollectionView->setObjectName( "CollectionView" );
100 mCollectionView->setSelectionMode( QAbstractItemView::ExtendedSelection );
101 splitter2->addWidget( mCollectionView );
103 EntityListView *favoritesView = new EntityListView( xmlGuiWindow, this );
104 //favoritesView->setViewMode( QListView::IconMode );
105 splitter2->addWidget( favoritesView );
107 splitter->addWidget( splitter2 );
109 Session *session = new Session( "AkonadiConsole Browser Widget", this );
111 // monitor collection changes
112 ChangeRecorder *monitor = new ChangeRecorder( this );
113 monitor->setSession(session);
114 monitor->setCollectionMonitored( Collection::root() );
115 monitor->fetchCollection( true );
116 monitor->setAllMonitored( true );
117 // TODO: Only fetch the envelope etc if possible.
118 monitor->itemFetchScope().fetchFullPayload(true);
120 mBrowserModel = new AkonadiBrowserModel( monitor, this );
121 mBrowserModel->setItemPopulationStrategy( EntityTreeModel::LazyPopulation );
122 mBrowserModel->setShowSystemEntities( true );
124 // new ModelTest( mBrowserModel );
126 EntityMimeTypeFilterModel *collectionFilter = new EntityMimeTypeFilterModel( this );
127 collectionFilter->setSourceModel( mBrowserModel );
128 collectionFilter->addMimeTypeInclusionFilter( Collection::mimeType() );
129 collectionFilter->setHeaderGroup( EntityTreeModel::CollectionTreeHeaders );
130 collectionFilter->setDynamicSortFilter( true );
131 collectionFilter->setSortCaseSensitivity( Qt::CaseInsensitive );
133 statisticsProxyModel = new StatisticsProxyModel( this );
134 statisticsProxyModel->setToolTipEnabled( true );
135 statisticsProxyModel->setSourceModel( collectionFilter );
137 QuotaColorProxyModel *quotaProxyModel = new QuotaColorProxyModel( this );
138 quotaProxyModel->setWarningThreshold( 50.0 );
139 quotaProxyModel->setSourceModel( statisticsProxyModel );
141 mCollectionView->setModel( quotaProxyModel );
143 Akonadi::SelectionProxyModel *selectionProxyModel = new Akonadi::SelectionProxyModel( mCollectionView->selectionModel(), this );
144 selectionProxyModel->setSourceModel( mBrowserModel );
145 selectionProxyModel->setFilterBehavior( KSelectionProxyModel::ChildrenOfExactSelection );
147 EntityMimeTypeFilterModel *itemFilter = new EntityMimeTypeFilterModel( this );
148 itemFilter->setSourceModel( selectionProxyModel );
149 itemFilter->addMimeTypeExclusionFilter( Collection::mimeType() );
150 itemFilter->setHeaderGroup( EntityTreeModel::ItemListHeaders );
152 const KConfigGroup group = KGlobal::config()->group( "FavoriteCollectionsModel" );
153 FavoriteCollectionsModel *favoritesModel = new FavoriteCollectionsModel( mBrowserModel, group, this );
154 favoritesView->setModel( favoritesModel );
156 QSplitter *splitter3 = new QSplitter( Qt::Vertical, this );
157 splitter3->setObjectName( "itemSplitter" );
158 splitter->addWidget( splitter3 );
160 QWidget *itemViewParent = new QWidget( this );
161 itemUi.setupUi( itemViewParent );
163 itemUi.modelBox->addItem( "Generic" );
164 itemUi.modelBox->addItem( "Mail" );
165 itemUi.modelBox->addItem( "Contacts" );
166 itemUi.modelBox->addItem( "Calendar" );
167 connect( itemUi.modelBox, SIGNAL(activated(int)), SLOT(modelChanged()) );
168 QTimer::singleShot( 0, this, SLOT(modelChanged()) );
170 itemUi.itemView->setXmlGuiClient( xmlGuiWindow );
171 itemUi.itemView->setModel( itemFilter );
172 itemUi.itemView->setSelectionMode( QAbstractItemView::ExtendedSelection );
173 connect( itemUi.itemView, SIGNAL( activated( QModelIndex ) ), SLOT(itemActivated( QModelIndex ) ) );
174 connect( itemUi.itemView, SIGNAL( clicked( QModelIndex ) ), SLOT(itemActivated( QModelIndex ) ) );
175 splitter3->addWidget( itemViewParent );
176 itemViewParent->layout()->setMargin( 0 );
178 QWidget *contentViewParent = new QWidget( this );
179 contentUi.setupUi( contentViewParent );
180 connect( contentUi.saveButton, SIGNAL(clicked()), SLOT(save()) );
181 splitter3->addWidget( contentViewParent );
183 connect( contentUi.attrAddButton, SIGNAL(clicked()), SLOT(addAttribute()) );
184 connect( contentUi.attrDeleteButton, SIGNAL(clicked()), SLOT(delAttribute()) );
186 CollectionPropertiesDialog::registerPage( new CollectionAclPageFactory() );
187 CollectionPropertiesDialog::registerPage( new CollectionAttributePageFactory() );
188 CollectionPropertiesDialog::registerPage( new CollectionInternalsPageFactory() );
190 Control::widgetNeedsAkonadi( this );
192 mStdActionManager = new StandardActionManager( xmlGuiWindow->actionCollection(), xmlGuiWindow );
193 mStdActionManager->setCollectionSelectionModel( mCollectionView->selectionModel() );
194 mStdActionManager->setItemSelectionModel( itemUi.itemView->selectionModel() );
195 mStdActionManager->setFavoriteCollectionsModel( favoritesModel );
196 mStdActionManager->setFavoriteSelectionModel( favoritesView->selectionModel() );
197 mStdActionManager->createAllActions();
199 Nepomuk::ResourceManager::instance()->init();
201 m_stateMaintainer = new Future::KViewStateMaintainer<ETMViewStateSaver>( KGlobal::config()->group("CollectionViewState"), this );
202 m_stateMaintainer->setView( mCollectionView );
204 m_stateMaintainer->restoreState();
207 BrowserWidget::~BrowserWidget()
209 m_stateMaintainer->saveState();
212 void BrowserWidget::clear()
214 contentUi.stack->setCurrentWidget( contentUi.unsupportedTypePage );
215 contentUi.dataView->clear();
216 contentUi.id->clear();
217 contentUi.remoteId->clear();
218 contentUi.mimeType->clear();
219 contentUi.revision->clear();
220 contentUi.size->clear();
221 contentUi.modificationtime->clear();
222 contentUi.flags->clear();
223 contentUi.attrView->setModel( 0 );
226 void BrowserWidget::itemActivated(const QModelIndex & index)
228 const Item item = index.sibling( index.row(), 0 ).data( EntityTreeModel::ItemRole ).value< Item >();
229 if ( !item.isValid() ) {
230 clear();
231 return;
234 ItemFetchJob *job = new ItemFetchJob( item, this );
235 job->fetchScope().fetchFullPayload();
236 job->fetchScope().fetchAllAttributes();
237 connect( job, SIGNAL( result( KJob* ) ), SLOT( itemFetchDone( KJob* ) ), Qt::QueuedConnection );
240 void BrowserWidget::itemFetchDone(KJob * job)
242 ItemFetchJob *fetch = static_cast<ItemFetchJob*>( job );
243 if ( job->error() ) {
244 kWarning( 5265 ) << "Item fetch failed: " << job->errorString();
245 } else if ( fetch->items().isEmpty() ) {
246 kWarning( 5265 ) << "No item found!";
247 } else {
248 const Item item = fetch->items().first();
249 setItem( item );
253 void BrowserWidget::setItem( const Akonadi::Item &item )
255 mCurrentItem = item;
256 if ( item.hasPayload<KABC::Addressee>() ) {
257 contentUi.contactView->setItem( item );
258 contentUi.stack->setCurrentWidget( contentUi.contactViewPage );
259 } else if ( item.hasPayload<KABC::ContactGroup>() ) {
260 contentUi.contactGroupView->setItem( item );
261 contentUi.stack->setCurrentWidget( contentUi.contactGroupViewPage );
262 } else if ( item.hasPayload<KCalCore::Incidence::Ptr>() ) {
263 contentUi.incidenceView->setItem( item );
264 contentUi.stack->setCurrentWidget( contentUi.incidenceViewPage );
265 } else if ( item.mimeType() == "message/rfc822"
266 || item.mimeType() == "message/news" ) {
267 contentUi.mailView->setMessageItem( item, MessageViewer::Viewer::Force );
268 contentUi.stack->setCurrentWidget( contentUi.mailViewPage );
269 } else if ( item.hasPayload<QPixmap>() ) {
270 contentUi.imageView->setPixmap( item.payload<QPixmap>() );
271 contentUi.stack->setCurrentWidget( contentUi.imageViewPage );
272 } else {
273 contentUi.stack->setCurrentWidget( contentUi.unsupportedTypePage );
276 QByteArray data = item.payloadData();
277 contentUi.dataView->setPlainText( data );
279 contentUi.id->setText( QString::number( item.id() ) );
280 contentUi.remoteId->setText( item.remoteId() );
281 contentUi.mimeType->setText( item.mimeType() );
282 contentUi.revision->setText( QString::number( item.revision() ) );
283 contentUi.size->setText( QString::number( item.size() ) );
284 contentUi.modificationtime->setText( item.modificationTime().toString() + ( " UTC" ) );
285 QStringList flags;
286 foreach ( const Item::Flag &f, item.flags() )
287 flags << QString::fromUtf8( f );
288 contentUi.flags->setItems( flags );
290 Attribute::List list = item.attributes();
291 delete mAttrModel;
292 mAttrModel = new QStandardItemModel( list.count(), 2 );
293 QStringList labels;
294 labels << i18n( "Attribute" ) << i18n( "Value" );
295 mAttrModel->setHorizontalHeaderLabels( labels );
296 for ( int i = 0; i < list.count(); ++i ) {
297 QModelIndex index = mAttrModel->index( i, 0 );
298 Q_ASSERT( index.isValid() );
299 mAttrModel->setData( index, QString::fromLatin1( list[i]->type() ) );
300 index = mAttrModel->index( i, 1 );
301 Q_ASSERT( index.isValid() );
302 mAttrModel->setData( index, QString::fromLatin1( list[i]->serialized() ) );
303 mAttrModel->itemFromIndex( index )->setFlags( Qt::ItemIsEditable | mAttrModel->flags( index ) );
305 contentUi.attrView->setModel( mAttrModel );
307 if ( Settings::self()->nepomukEnabled() ) {
308 Nepomuk::Resource res( item.url() );
310 contentUi.tagWidget->setTaggedResource( res );
311 contentUi.ratingWidget->setRating( res.rating() );
313 delete mNepomukModel;
314 mNepomukModel = 0;
315 if ( res.isValid() ) {
316 contentUi.rdfClassName->setText( res.className() );
317 QHash<QUrl, Nepomuk::Variant> props = res.properties();
318 mNepomukModel = new QStandardItemModel( props.count(), 2, this );
319 QStringList labels;
320 labels << i18n( "Property" ) << i18n( "Value" );
321 mNepomukModel->setHorizontalHeaderLabels( labels );
322 int row = 0;
323 for ( QHash<QUrl, Nepomuk::Variant>::ConstIterator it = props.constBegin(); it != props.constEnd(); ++it, ++row ) {
324 QModelIndex index = mNepomukModel->index( row, 0 );
325 Q_ASSERT( index.isValid() );
326 mNepomukModel->setData( index, it.key().toString() );
327 index = mNepomukModel->index( row, 1 );
328 Q_ASSERT( index.isValid() );
329 mNepomukModel->setData( index, it.value().toString() );
331 contentUi.nepomukView->setEnabled( true );
332 } else {
333 contentUi.nepomukView->setEnabled( false );
335 contentUi.nepomukView->setModel( mNepomukModel );
336 contentUi.nepomukTab->setEnabled( true );
337 } else {
338 contentUi.nepomukTab->setEnabled( false );
341 if ( mMonitor )
342 mMonitor->deleteLater(); // might be the one calling us
343 mMonitor = new Monitor( this );
344 mMonitor->setItemMonitored( item );
345 mMonitor->itemFetchScope().fetchFullPayload();
346 mMonitor->itemFetchScope().fetchAllAttributes();
347 qRegisterMetaType<QSet<QByteArray> >();
348 connect( mMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)), SLOT(setItem(Akonadi::Item)), Qt::QueuedConnection );
351 void BrowserWidget::modelChanged()
353 switch ( itemUi.modelBox->currentIndex() ) {
354 case 1:
355 mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::MailMode);
356 break;
357 case 2:
358 mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::ContactsMode);
359 break;
360 case 3:
361 mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::CalendarMode);
362 break;
363 default:
364 mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::GenericMode);
368 void BrowserWidget::save()
370 Q_ASSERT( mAttrModel );
372 const QByteArray data = contentUi.dataView->toPlainText().toUtf8();
373 Item item = mCurrentItem;
374 item.setRemoteId( contentUi.remoteId->text() );
375 foreach ( const Item::Flag &f, mCurrentItem.flags() )
376 item.clearFlag( f );
377 foreach ( const QString &s, contentUi.flags->items() )
378 item.setFlag( s.toUtf8() );
379 item.setPayloadFromData( data );
381 item.clearAttributes();
382 for ( int i = 0; i < mAttrModel->rowCount(); ++i ) {
383 const QModelIndex typeIndex = mAttrModel->index( i, 0 );
384 Q_ASSERT( typeIndex.isValid() );
385 const QModelIndex valueIndex = mAttrModel->index( i, 1 );
386 Q_ASSERT( valueIndex.isValid() );
387 Attribute* attr = AttributeFactory::createAttribute( mAttrModel->data( typeIndex ).toString().toLatin1() );
388 Q_ASSERT( attr );
389 attr->deserialize( mAttrModel->data( valueIndex ).toString().toLatin1() );
390 item.addAttribute( attr );
393 ItemModifyJob *store = new ItemModifyJob( item, this );
394 connect( store, SIGNAL(result(KJob*)), SLOT(saveResult(KJob*)) );
396 if ( Settings::self()->nepomukEnabled() ) {
397 Nepomuk::Resource res( item.url() );
398 res.setRating( contentUi.ratingWidget->rating() );
402 void BrowserWidget::saveResult(KJob * job)
404 if ( job->error() ) {
405 KMessageBox::error( this, i18n( "Failed to save changes: %1", job->errorString() ) );
409 void BrowserWidget::addAttribute()
411 if ( !mAttrModel || contentUi.attrName->text().isEmpty() )
412 return;
413 const int row = mAttrModel->rowCount();
414 mAttrModel->insertRow( row );
415 QModelIndex index = mAttrModel->index( row, 0 );
416 Q_ASSERT( index.isValid() );
417 mAttrModel->setData( index, contentUi.attrName->text() );
418 contentUi.attrName->clear();
421 void BrowserWidget::delAttribute()
423 if ( !mAttrModel )
424 return;
425 QModelIndexList selection = contentUi.attrView->selectionModel()->selectedRows();
426 if ( selection.count() != 1 )
427 return;
428 mAttrModel->removeRow( selection.first().row() );
431 void BrowserWidget::dumpToXml()
433 const Collection root = mCollectionView->currentIndex().data( EntityTreeModel::CollectionRole ).value<Collection>();
434 if ( !root.isValid() )
435 return;
436 const QString fileName = KFileDialog::getSaveFileName( KUrl(), "*.xml", this, i18n( "Select XML file" ) );
437 if ( fileName.isEmpty() )
438 return;
439 #if 0 // TODO: port me, can't use XmlWriteJob here, it's in runtime, call the akonadi2xml cli tool instead
440 XmlWriteJob *job = new XmlWriteJob( root, fileName, this );
441 connect( job, SIGNAL(result(KJob*)), SLOT(dumpToXmlResult(KJob*)) );
442 #endif
445 void BrowserWidget::dumpToXmlResult( KJob* job )
447 if ( job->error() )
448 KMessageBox::error( this, job->errorString() );
451 #include "browserwidget.moc"