mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
1337 lines
55 KiB
C++
1337 lines
55 KiB
C++
/*
|
|
* collectionmodel.cpp - Akonadi collection models
|
|
* Program: kalarm
|
|
* Copyright © 2007-2012 by David Jarvie <djarvie@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "collectionmodel.h"
|
|
#include "autoqpointer.h"
|
|
#include "messagebox.h"
|
|
#include "preferences.h"
|
|
|
|
#include <kalarmcal/collectionattribute.h>
|
|
#include <kalarmcal/compatibilityattribute.h>
|
|
|
|
#include <akonadi/agentmanager.h>
|
|
#include <akonadi/collectiondialog.h>
|
|
#include <akonadi/collectiondeletejob.h>
|
|
#include <akonadi/collectionmodifyjob.h>
|
|
#include <akonadi/entitymimetypefiltermodel.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <QApplication>
|
|
#include <QMouseEvent>
|
|
#include <QHelpEvent>
|
|
#include <QToolTip>
|
|
#include <QTimer>
|
|
#include <QObject>
|
|
|
|
using namespace Akonadi;
|
|
using namespace KAlarmCal;
|
|
|
|
static Collection::Rights writableRights = Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem;
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionMimeTypeFilterModel
|
|
= Proxy model to filter AkonadiModel to restrict its contents to Collections,
|
|
= not Items, containing specified KAlarm content mime types.
|
|
= It can optionally be restricted to writable and/or enabled Collections.
|
|
=============================================================================*/
|
|
class CollectionMimeTypeFilterModel : public Akonadi::EntityMimeTypeFilterModel
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit CollectionMimeTypeFilterModel(QObject* parent = 0);
|
|
void setEventTypeFilter(CalEvent::Type);
|
|
void setFilterWritable(bool writable);
|
|
void setFilterEnabled(bool enabled);
|
|
Akonadi::Collection collection(int row) const;
|
|
Akonadi::Collection collection(const QModelIndex&) const;
|
|
QModelIndex collectionIndex(const Akonadi::Collection&) const;
|
|
|
|
protected:
|
|
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const;
|
|
|
|
private:
|
|
CalEvent::Type mAlarmType; // collection content type contained in this model
|
|
bool mWritableOnly; // only include writable collections in this model
|
|
bool mEnabledOnly; // only include enabled collections in this model
|
|
};
|
|
|
|
CollectionMimeTypeFilterModel::CollectionMimeTypeFilterModel(QObject* parent)
|
|
: EntityMimeTypeFilterModel(parent),
|
|
mAlarmType(CalEvent::EMPTY),
|
|
mWritableOnly(false),
|
|
mEnabledOnly(false)
|
|
{
|
|
addMimeTypeInclusionFilter(Collection::mimeType()); // select collections, not items
|
|
setHeaderGroup(EntityTreeModel::CollectionTreeHeaders);
|
|
setSourceModel(AkonadiModel::instance());
|
|
}
|
|
|
|
void CollectionMimeTypeFilterModel::setEventTypeFilter(CalEvent::Type type)
|
|
{
|
|
if (type != mAlarmType)
|
|
{
|
|
mAlarmType = type;
|
|
invalidateFilter();
|
|
}
|
|
}
|
|
|
|
void CollectionMimeTypeFilterModel::setFilterWritable(bool writable)
|
|
{
|
|
if (writable != mWritableOnly)
|
|
{
|
|
mWritableOnly = writable;
|
|
invalidateFilter();
|
|
}
|
|
}
|
|
|
|
void CollectionMimeTypeFilterModel::setFilterEnabled(bool enabled)
|
|
{
|
|
if (enabled != mEnabledOnly)
|
|
{
|
|
emit layoutAboutToBeChanged();
|
|
mEnabledOnly = enabled;
|
|
invalidateFilter();
|
|
emit layoutChanged();
|
|
}
|
|
}
|
|
|
|
bool CollectionMimeTypeFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
|
{
|
|
if (!EntityMimeTypeFilterModel::filterAcceptsRow(sourceRow, sourceParent))
|
|
return false;
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
const QModelIndex ix = model->index(sourceRow, 0, sourceParent);
|
|
const Collection collection = model->data(ix, AkonadiModel::CollectionRole).value<Collection>();
|
|
if (!AgentManager::self()->instance(collection.resource()).isValid())
|
|
return false;
|
|
if (!mWritableOnly && mAlarmType == CalEvent::EMPTY)
|
|
return true;
|
|
if (mWritableOnly && (collection.rights() & writableRights) != writableRights)
|
|
return false;
|
|
if (mAlarmType != CalEvent::EMPTY && !collection.contentMimeTypes().contains(CalEvent::mimeType(mAlarmType)))
|
|
return false;
|
|
if ((mWritableOnly || mEnabledOnly) && !collection.hasAttribute<CollectionAttribute>())
|
|
return false;
|
|
if (mWritableOnly && (!collection.hasAttribute<CompatibilityAttribute>()
|
|
|| collection.attribute<CompatibilityAttribute>()->compatibility() != KACalendar::Current))
|
|
return false;
|
|
if (mEnabledOnly && !collection.attribute<CollectionAttribute>()->isEnabled(mAlarmType))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the collection for a given row.
|
|
*/
|
|
Collection CollectionMimeTypeFilterModel::collection(int row) const
|
|
{
|
|
return static_cast<AkonadiModel*>(sourceModel())->data(mapToSource(index(row, 0)), EntityTreeModel::CollectionRole).value<Collection>();
|
|
}
|
|
|
|
Collection CollectionMimeTypeFilterModel::collection(const QModelIndex& index) const
|
|
{
|
|
return static_cast<AkonadiModel*>(sourceModel())->data(mapToSource(index), EntityTreeModel::CollectionRole).value<Collection>();
|
|
}
|
|
|
|
QModelIndex CollectionMimeTypeFilterModel::collectionIndex(const Collection& collection) const
|
|
{
|
|
return mapFromSource(static_cast<AkonadiModel*>(sourceModel())->collectionIndex(collection));
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionListModel
|
|
= Proxy model converting the AkonadiModel collection tree into a flat list.
|
|
= The model may be restricted to specified content mime types.
|
|
= It can optionally be restricted to writable and/or enabled Collections.
|
|
=============================================================================*/
|
|
|
|
CollectionListModel::CollectionListModel(QObject* parent)
|
|
: KDescendantsProxyModel(parent),
|
|
mUseCollectionColour(true)
|
|
{
|
|
setSourceModel(new CollectionMimeTypeFilterModel(this));
|
|
setDisplayAncestorData(false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the collection for a given row.
|
|
*/
|
|
Collection CollectionListModel::collection(int row) const
|
|
{
|
|
return data(index(row, 0), EntityTreeModel::CollectionRole).value<Collection>();
|
|
}
|
|
|
|
Collection CollectionListModel::collection(const QModelIndex& index) const
|
|
{
|
|
return data(index, EntityTreeModel::CollectionRole).value<Collection>();
|
|
}
|
|
|
|
QModelIndex CollectionListModel::collectionIndex(const Collection& collection) const
|
|
{
|
|
return mapFromSource(static_cast<CollectionMimeTypeFilterModel*>(sourceModel())->collectionIndex(collection));
|
|
}
|
|
|
|
void CollectionListModel::setEventTypeFilter(CalEvent::Type type)
|
|
{
|
|
static_cast<CollectionMimeTypeFilterModel*>(sourceModel())->setEventTypeFilter(type);
|
|
}
|
|
|
|
void CollectionListModel::setFilterWritable(bool writable)
|
|
{
|
|
static_cast<CollectionMimeTypeFilterModel*>(sourceModel())->setFilterWritable(writable);
|
|
}
|
|
|
|
void CollectionListModel::setFilterEnabled(bool enabled)
|
|
{
|
|
static_cast<CollectionMimeTypeFilterModel*>(sourceModel())->setFilterEnabled(enabled);
|
|
}
|
|
|
|
bool CollectionListModel::isDescendantOf(const QModelIndex& ancestor, const QModelIndex& descendant) const
|
|
{
|
|
Q_UNUSED(descendant);
|
|
return !ancestor.isValid();
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the data for a given role, for a specified item.
|
|
*/
|
|
QVariant CollectionListModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
switch (role)
|
|
{
|
|
case Qt::BackgroundRole:
|
|
if (!mUseCollectionColour)
|
|
role = AkonadiModel::BaseColourRole;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return KDescendantsProxyModel::data(index, role);
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionCheckListModel
|
|
= Proxy model providing a checkable list of all Collections. A Collection's
|
|
= checked status is equivalent to whether it is selected or not.
|
|
= An alarm type is specified, whereby Collections which are enabled for that
|
|
= alarm type are checked; Collections which do not contain that alarm type, or
|
|
= which are disabled for that alarm type, are unchecked.
|
|
=============================================================================*/
|
|
|
|
CollectionListModel* CollectionCheckListModel::mModel = 0;
|
|
int CollectionCheckListModel::mInstanceCount = 0;
|
|
|
|
CollectionCheckListModel::CollectionCheckListModel(CalEvent::Type type, QObject* parent)
|
|
: KCheckableProxyModel(parent),
|
|
mAlarmType(type)
|
|
{
|
|
++mInstanceCount;
|
|
if (!mModel)
|
|
mModel = new CollectionListModel(0);
|
|
setSourceModel(mModel); // the source model is NOT filtered by alarm type
|
|
mSelectionModel = new QItemSelectionModel(mModel);
|
|
setSelectionModel(mSelectionModel);
|
|
connect(mSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
SLOT(selectionChanged(QItemSelection,QItemSelection)));
|
|
connect(mModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), SIGNAL(layoutAboutToBeChanged()));
|
|
connect(mModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int)));
|
|
// This is probably needed to make CollectionFilterCheckListModel update
|
|
// (similarly to when rows are inserted).
|
|
connect(mModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SIGNAL(layoutAboutToBeChanged()));
|
|
connect(mModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(layoutChanged()));
|
|
|
|
connect(AkonadiModel::instance(), SIGNAL(collectionStatusChanged(Akonadi::Collection,AkonadiModel::Change,QVariant,bool)),
|
|
SLOT(collectionStatusChanged(Akonadi::Collection,AkonadiModel::Change,QVariant,bool)));
|
|
|
|
// Initialise checked status for all collections.
|
|
// Note that this is only necessary if the model is recreated after
|
|
// being deleted.
|
|
for (int row = 0, count = mModel->rowCount(); row < count; ++row)
|
|
setSelectionStatus(mModel->collection(row), mModel->index(row, 0));
|
|
}
|
|
|
|
CollectionCheckListModel::~CollectionCheckListModel()
|
|
{
|
|
if (--mInstanceCount <= 0)
|
|
{
|
|
delete mModel;
|
|
mModel = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Return the collection for a given row.
|
|
*/
|
|
Collection CollectionCheckListModel::collection(int row) const
|
|
{
|
|
return mModel->collection(mapToSource(index(row, 0)));
|
|
}
|
|
|
|
Collection CollectionCheckListModel::collection(const QModelIndex& index) const
|
|
{
|
|
return mModel->collection(mapToSource(index));
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return model data for one index.
|
|
*/
|
|
QVariant CollectionCheckListModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
const Collection collection = mModel->collection(index);
|
|
if (collection.isValid())
|
|
{
|
|
// This is a Collection row
|
|
switch (role)
|
|
{
|
|
case Qt::ForegroundRole:
|
|
{
|
|
const QString mimeType = CalEvent::mimeType(mAlarmType);
|
|
if (collection.contentMimeTypes().contains(mimeType))
|
|
return AkonadiModel::foregroundColor(collection, QStringList(mimeType));
|
|
break;
|
|
}
|
|
case Qt::FontRole:
|
|
{
|
|
if (!collection.hasAttribute<CollectionAttribute>()
|
|
|| !AkonadiModel::isCompatible(collection))
|
|
break;
|
|
const CollectionAttribute* attr = collection.attribute<CollectionAttribute>();
|
|
if (!attr->enabled())
|
|
break;
|
|
const QStringList mimeTypes = collection.contentMimeTypes();
|
|
if (attr->isStandard(mAlarmType) && mimeTypes.contains(CalEvent::mimeType(mAlarmType)))
|
|
{
|
|
// It's the standard collection for a mime type
|
|
QFont font = qvariant_cast<QFont>(KCheckableProxyModel::data(index, role));
|
|
font.setBold(true);
|
|
return font;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return KCheckableProxyModel::data(index, role);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Set model data for one index.
|
|
* If the change is to disable a collection, check for eligibility and prevent
|
|
* the change if necessary.
|
|
*/
|
|
bool CollectionCheckListModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (role == Qt::CheckStateRole && static_cast<Qt::CheckState>(value.toInt()) != Qt::Checked)
|
|
{
|
|
// A collection is to be disabled.
|
|
const Collection collection = mModel->collection(index);
|
|
if (collection.isValid() && collection.hasAttribute<CollectionAttribute>())
|
|
{
|
|
const CollectionAttribute* attr = collection.attribute<CollectionAttribute>();
|
|
if (attr->isEnabled(mAlarmType))
|
|
{
|
|
QString errmsg;
|
|
QWidget* messageParent = qobject_cast<QWidget*>(QObject::parent());
|
|
if (attr->isStandard(mAlarmType)
|
|
&& AkonadiModel::isCompatible(collection))
|
|
{
|
|
// It's the standard collection for some alarm type.
|
|
if (mAlarmType == CalEvent::ACTIVE)
|
|
{
|
|
errmsg = i18nc("@info", "You cannot disable your default active alarm calendar.");
|
|
}
|
|
else if (mAlarmType == CalEvent::ARCHIVED && Preferences::archivedKeepDays())
|
|
{
|
|
// Only allow the archived alarms standard collection to be disabled if
|
|
// we're not saving expired alarms.
|
|
errmsg = i18nc("@info", "You cannot disable your default archived alarm calendar "
|
|
"while expired alarms are configured to be kept.");
|
|
}
|
|
else if (KAMessageBox::warningContinueCancel(messageParent,
|
|
i18nc("@info", "Do you really want to disable your default calendar?"))
|
|
== KMessageBox::Cancel)
|
|
return false;
|
|
}
|
|
if (!errmsg.isEmpty())
|
|
{
|
|
KAMessageBox::sorry(messageParent, errmsg);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return KCheckableProxyModel::setData(index, value, role);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when rows have been inserted into the model.
|
|
* Select or deselect them according to their enabled status.
|
|
*/
|
|
void CollectionCheckListModel::slotRowsInserted(const QModelIndex& parent, int start, int end)
|
|
{
|
|
for (int row = start; row <= end; ++row)
|
|
{
|
|
const QModelIndex ix = mapToSource(index(row, 0, parent));
|
|
const Collection collection = mModel->collection(ix);
|
|
if (collection.isValid())
|
|
setSelectionStatus(collection, ix);
|
|
}
|
|
emit layoutChanged(); // this is needed to make CollectionFilterCheckListModel update
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when the user has ticked/unticked a collection to enable/disable it
|
|
* (or when the selection changes for any other reason).
|
|
*/
|
|
void CollectionCheckListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
|
{
|
|
const QModelIndexList sel = selected.indexes();
|
|
foreach (const QModelIndex& ix, sel)
|
|
{
|
|
// Try to enable the collection, but untick it if not possible
|
|
if (!CollectionControlModel::setEnabled(mModel->collection(ix), mAlarmType, true))
|
|
mSelectionModel->select(ix, QItemSelectionModel::Deselect);
|
|
}
|
|
const QModelIndexList desel = deselected.indexes();
|
|
foreach (const QModelIndex& ix, desel)
|
|
CollectionControlModel::setEnabled(mModel->collection(ix), mAlarmType, false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when a collection parameter or status has changed.
|
|
* If the collection's alarm types have been reconfigured, ensure that the
|
|
* model views are updated to reflect this.
|
|
*/
|
|
void CollectionCheckListModel::collectionStatusChanged(const Collection& collection, AkonadiModel::Change change, const QVariant&, bool inserted)
|
|
{
|
|
if (inserted || !collection.isValid())
|
|
return;
|
|
switch (change)
|
|
{
|
|
case AkonadiModel::Enabled:
|
|
kDebug() << "Enabled" << collection.id();
|
|
break;
|
|
case AkonadiModel::AlarmTypes:
|
|
kDebug() << "AlarmTypes" << collection.id();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
const QModelIndex ix = mModel->collectionIndex(collection);
|
|
if (ix.isValid())
|
|
setSelectionStatus(collection, ix);
|
|
if (change == AkonadiModel::AlarmTypes)
|
|
emit collectionTypeChange(this);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Select or deselect an index according to its enabled status.
|
|
*/
|
|
void CollectionCheckListModel::setSelectionStatus(const Collection& collection, const QModelIndex& sourceIndex)
|
|
{
|
|
const QItemSelectionModel::SelectionFlags sel = (collection.hasAttribute<CollectionAttribute>()
|
|
&& collection.attribute<CollectionAttribute>()->isEnabled(mAlarmType))
|
|
? QItemSelectionModel::Select : QItemSelectionModel::Deselect;
|
|
mSelectionModel->select(sourceIndex, sel);
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionFilterCheckListModel
|
|
= Proxy model providing a checkable collection list. The model contains all
|
|
= alarm types, but returns only one type at any given time. The selected alarm
|
|
= type may be changed as desired.
|
|
=============================================================================*/
|
|
CollectionFilterCheckListModel::CollectionFilterCheckListModel(QObject* parent)
|
|
: QSortFilterProxyModel(parent),
|
|
mActiveModel(new CollectionCheckListModel(CalEvent::ACTIVE, this)),
|
|
mArchivedModel(new CollectionCheckListModel(CalEvent::ARCHIVED, this)),
|
|
mTemplateModel(new CollectionCheckListModel(CalEvent::TEMPLATE, this)),
|
|
mAlarmType(CalEvent::EMPTY)
|
|
{
|
|
setDynamicSortFilter(true);
|
|
connect(mActiveModel, SIGNAL(collectionTypeChange(CollectionCheckListModel*)), SLOT(collectionTypeChanged(CollectionCheckListModel*)));
|
|
connect(mArchivedModel, SIGNAL(collectionTypeChange(CollectionCheckListModel*)), SLOT(collectionTypeChanged(CollectionCheckListModel*)));
|
|
connect(mTemplateModel, SIGNAL(collectionTypeChange(CollectionCheckListModel*)), SLOT(collectionTypeChanged(CollectionCheckListModel*)));
|
|
}
|
|
|
|
void CollectionFilterCheckListModel::setEventTypeFilter(CalEvent::Type type)
|
|
{
|
|
if (type != mAlarmType)
|
|
{
|
|
CollectionCheckListModel* newModel;
|
|
switch (type)
|
|
{
|
|
case CalEvent::ACTIVE: newModel = mActiveModel; break;
|
|
case CalEvent::ARCHIVED: newModel = mArchivedModel; break;
|
|
case CalEvent::TEMPLATE: newModel = mTemplateModel; break;
|
|
default:
|
|
return;
|
|
}
|
|
mAlarmType = type;
|
|
setSourceModel(newModel);
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the collection for a given row.
|
|
*/
|
|
Collection CollectionFilterCheckListModel::collection(int row) const
|
|
{
|
|
return static_cast<CollectionCheckListModel*>(sourceModel())->collection(mapToSource(index(row, 0)));
|
|
}
|
|
|
|
Collection CollectionFilterCheckListModel::collection(const QModelIndex& index) const
|
|
{
|
|
return static_cast<CollectionCheckListModel*>(sourceModel())->collection(mapToSource(index));
|
|
}
|
|
|
|
QVariant CollectionFilterCheckListModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
switch (role)
|
|
{
|
|
case Qt::ToolTipRole:
|
|
{
|
|
const Collection col = collection(index);
|
|
if (col.isValid())
|
|
return AkonadiModel::instance()->tooltip(col, mAlarmType);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return QSortFilterProxyModel::data(index, role);
|
|
}
|
|
|
|
bool CollectionFilterCheckListModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
|
{
|
|
if (mAlarmType == CalEvent::EMPTY)
|
|
return true;
|
|
const CollectionCheckListModel* model = static_cast<CollectionCheckListModel*>(sourceModel());
|
|
const Collection collection = model->collection(model->index(sourceRow, 0, sourceParent));
|
|
return collection.contentMimeTypes().contains(CalEvent::mimeType(mAlarmType));
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when a collection alarm type has changed.
|
|
* Ensure that the collection is removed from or added to the current model view.
|
|
*/
|
|
void CollectionFilterCheckListModel::collectionTypeChanged(CollectionCheckListModel* model)
|
|
{
|
|
if (model == sourceModel())
|
|
invalidateFilter();
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionView
|
|
= View displaying a list of collections.
|
|
=============================================================================*/
|
|
CollectionView::CollectionView(CollectionFilterCheckListModel* model, QWidget* parent)
|
|
: QListView(parent)
|
|
{
|
|
setModel(model);
|
|
}
|
|
|
|
void CollectionView::setModel(QAbstractItemModel* model)
|
|
{
|
|
QListView::setModel(model);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the collection for a given row.
|
|
*/
|
|
Collection CollectionView::collection(int row) const
|
|
{
|
|
return static_cast<CollectionFilterCheckListModel*>(model())->collection(row);
|
|
}
|
|
|
|
Collection CollectionView::collection(const QModelIndex& index) const
|
|
{
|
|
return static_cast<CollectionFilterCheckListModel*>(model())->collection(index);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when a mouse button is released.
|
|
* Any currently selected collection is deselected.
|
|
*/
|
|
void CollectionView::mouseReleaseEvent(QMouseEvent* e)
|
|
{
|
|
if (!indexAt(e->pos()).isValid())
|
|
clearSelection();
|
|
QListView::mouseReleaseEvent(e);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when a ToolTip or WhatsThis event occurs.
|
|
*/
|
|
bool CollectionView::viewportEvent(QEvent* e)
|
|
{
|
|
if (e->type() == QEvent::ToolTip && isActiveWindow())
|
|
{
|
|
const QHelpEvent* he = static_cast<QHelpEvent*>(e);
|
|
const QModelIndex index = indexAt(he->pos());
|
|
QVariant value = static_cast<CollectionFilterCheckListModel*>(model())->data(index, Qt::ToolTipRole);
|
|
if (qVariantCanConvert<QString>(value))
|
|
{
|
|
QString toolTip = value.toString();
|
|
int i = toolTip.indexOf(QLatin1Char('@'));
|
|
if (i > 0)
|
|
{
|
|
int j = toolTip.indexOf(QRegExp(QLatin1String("<(nl|br)"), Qt::CaseInsensitive), i + 1);
|
|
int k = toolTip.indexOf(QLatin1Char('@'), j);
|
|
const QString name = toolTip.mid(i + 1, j - i - 1);
|
|
value = model()->data(index, Qt::FontRole);
|
|
const QFontMetrics fm(qvariant_cast<QFont>(value).resolve(viewOptions().font));
|
|
int textWidth = fm.boundingRect(name).width() + 1;
|
|
const int margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
|
|
QStyleOptionButton opt;
|
|
opt.QStyleOption::operator=(viewOptions());
|
|
opt.rect = rectForIndex(index);
|
|
int checkWidth = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt).width();
|
|
int left = spacing() + 3*margin + checkWidth + viewOptions().decorationSize.width(); // left offset of text
|
|
int right = left + textWidth;
|
|
if (left >= horizontalOffset() + spacing()
|
|
&& right <= horizontalOffset() + width() - spacing() - 2*frameWidth())
|
|
{
|
|
// The whole of the collection name is already displayed,
|
|
// so omit it from the tooltip.
|
|
if (k > 0)
|
|
toolTip.remove(i, k + 1 - i);
|
|
}
|
|
else
|
|
{
|
|
toolTip.remove(k, 1);
|
|
toolTip.remove(i, 1);
|
|
}
|
|
}
|
|
QToolTip::showText(he->globalPos(), toolTip, this);
|
|
return true;
|
|
}
|
|
}
|
|
return QListView::viewportEvent(e);
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
= Class: CollectionControlModel
|
|
= Proxy model to select which Collections will be enabled. Disabled Collections
|
|
= are not populated or monitored; their contents are ignored. The set of
|
|
= enabled Collections is stored in the config file's "Collections" group.
|
|
= Note that this model is not used directly for displaying - its purpose is to
|
|
= allow collections to be disabled, which will remove them from the other
|
|
= collection models.
|
|
=============================================================================*/
|
|
|
|
CollectionControlModel* CollectionControlModel::mInstance = 0;
|
|
bool CollectionControlModel::mAskDestination = false;
|
|
|
|
CollectionControlModel* CollectionControlModel::instance()
|
|
{
|
|
if (!mInstance)
|
|
mInstance = new CollectionControlModel(qApp);
|
|
return mInstance;
|
|
}
|
|
|
|
CollectionControlModel::CollectionControlModel(QObject* parent)
|
|
: FavoriteCollectionsModel(AkonadiModel::instance(), KConfigGroup(KGlobal::config(), "Collections"), parent),
|
|
mPopulatedCheckLoop(0)
|
|
{
|
|
// Initialise the list of enabled collections
|
|
EntityMimeTypeFilterModel* filter = new EntityMimeTypeFilterModel(this);
|
|
filter->addMimeTypeInclusionFilter(Collection::mimeType());
|
|
filter->setSourceModel(AkonadiModel::instance());
|
|
Collection::List collections;
|
|
findEnabledCollections(filter, QModelIndex(), collections);
|
|
setCollections(collections);
|
|
|
|
connect(AkonadiModel::instance(), SIGNAL(collectionStatusChanged(Akonadi::Collection,AkonadiModel::Change,QVariant,bool)),
|
|
SLOT(statusChanged(Akonadi::Collection,AkonadiModel::Change,QVariant,bool)));
|
|
#if KDE_IS_VERSION(4,9,80)
|
|
connect(AkonadiModel::instance(), SIGNAL(collectionTreeFetched(Akonadi::Collection::List)),
|
|
SLOT(collectionPopulated()));
|
|
connect(AkonadiModel::instance(), SIGNAL(collectionPopulated(Akonadi::Collection::Id)),
|
|
SLOT(collectionPopulated()));
|
|
#endif
|
|
connect(AkonadiModel::instance(), SIGNAL(serverStopped()), SLOT(reset()));
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Recursive function to check all collections' enabled status, and to compile a
|
|
* list of all collections which have any alarm types enabled.
|
|
* Collections which duplicate the same backend storage are filtered out, to
|
|
* avoid crashes due to duplicate events in different resources.
|
|
*/
|
|
void CollectionControlModel::findEnabledCollections(const EntityMimeTypeFilterModel* filter, const QModelIndex& parent, Collection::List& collections) const
|
|
{
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
for (int row = 0, count = filter->rowCount(parent); row < count; ++row)
|
|
{
|
|
const QModelIndex ix = filter->index(row, 0, parent);
|
|
const Collection collection = model->data(filter->mapToSource(ix), AkonadiModel::CollectionRole).value<Collection>();
|
|
if (!AgentManager::self()->instance(collection.resource()).isValid())
|
|
continue; // the collection doesn't belong to a resource, so omit it
|
|
const CalEvent::Types enabled = !collection.hasAttribute<CollectionAttribute>() ? CalEvent::EMPTY
|
|
: collection.attribute<CollectionAttribute>()->enabled();
|
|
const CalEvent::Types canEnable = checkTypesToEnable(collection, collections, enabled);
|
|
if (canEnable != enabled)
|
|
{
|
|
// There is another collection which uses the same backend
|
|
// storage. Disable alarm types enabled in the other collection.
|
|
if (!model->isCollectionBeingDeleted(collection.id()))
|
|
model->setData(model->collectionIndex(collection), static_cast<int>(canEnable), AkonadiModel::EnabledTypesRole);
|
|
}
|
|
if (canEnable)
|
|
collections += collection;
|
|
if (filter->rowCount(ix) > 0)
|
|
findEnabledCollections(filter, ix, collections);
|
|
}
|
|
}
|
|
|
|
bool CollectionControlModel::isEnabled(const Collection& collection, CalEvent::Type type)
|
|
{
|
|
if (!collection.isValid() || !instance()->collections().contains(collection))
|
|
return false;
|
|
if (!AgentManager::self()->instance(collection.resource()).isValid())
|
|
{
|
|
// The collection doesn't belong to a resource, so it can't be used.
|
|
// Remove it from the list of collections.
|
|
instance()->removeCollection(collection);
|
|
return false;
|
|
}
|
|
Collection col = collection;
|
|
AkonadiModel::instance()->refresh(col); // update with latest data
|
|
return col.hasAttribute<CollectionAttribute>()
|
|
&& col.attribute<CollectionAttribute>()->isEnabled(type);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Enable or disable the specified alarm types for a collection.
|
|
* Reply = alarm types which can be enabled
|
|
*/
|
|
CalEvent::Types CollectionControlModel::setEnabled(const Collection& collection, CalEvent::Types types, bool enabled)
|
|
{
|
|
kDebug() << "id:" << collection.id() << ", alarm types" << types << "->" << enabled;
|
|
if (!collection.isValid() || (!enabled && !instance()->collections().contains(collection)))
|
|
return CalEvent::EMPTY;
|
|
Collection col = collection;
|
|
AkonadiModel::instance()->refresh(col); // update with latest data
|
|
CalEvent::Types alarmTypes = !col.hasAttribute<CollectionAttribute>() ? CalEvent::EMPTY
|
|
: col.attribute<CollectionAttribute>()->enabled();
|
|
if (enabled)
|
|
alarmTypes |= static_cast<CalEvent::Types>(types & (CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE));
|
|
else
|
|
alarmTypes &= ~types;
|
|
|
|
return instance()->setEnabledStatus(collection, alarmTypes, false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Change the collection's enabled status.
|
|
* Add or remove the collection to/from the enabled list.
|
|
* Reply = alarm types which can be enabled
|
|
*/
|
|
CalEvent::Types CollectionControlModel::setEnabledStatus(const Collection& collection, CalEvent::Types types, bool inserted)
|
|
{
|
|
kDebug() << "id:" << collection.id() << ", types=" << types;
|
|
CalEvent::Types disallowedStdTypes(0);
|
|
CalEvent::Types stdTypes(0);
|
|
|
|
// Prevent the enabling of duplicate alarm types if another collection
|
|
// uses the same backend storage.
|
|
const Collection::List cols = collections();
|
|
const CalEvent::Types canEnable = checkTypesToEnable(collection, cols, types);
|
|
|
|
// Update the list of enabled collections
|
|
if (canEnable)
|
|
{
|
|
bool inList = false;
|
|
const Collection::List cols = collections();
|
|
foreach (const Collection& c, cols)
|
|
{
|
|
if (c.id() == collection.id())
|
|
{
|
|
inList = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!inList)
|
|
{
|
|
// It's a new collection.
|
|
// Prevent duplicate standard collections being created for any alarm type.
|
|
stdTypes = collection.hasAttribute<CollectionAttribute>()
|
|
? collection.attribute<CollectionAttribute>()->standard()
|
|
: CalEvent::EMPTY;
|
|
if (stdTypes)
|
|
{
|
|
foreach (const Collection& col, cols)
|
|
{
|
|
Collection c(col);
|
|
AkonadiModel::instance()->refresh(c); // update with latest data
|
|
if (c.isValid())
|
|
{
|
|
const CalEvent::Types t = stdTypes & CalEvent::types(c.contentMimeTypes());
|
|
if (t)
|
|
{
|
|
if (c.hasAttribute<CollectionAttribute>()
|
|
&& AkonadiModel::isCompatible(c))
|
|
{
|
|
disallowedStdTypes |= c.attribute<CollectionAttribute>()->standard() & t;
|
|
if (disallowedStdTypes == stdTypes)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
addCollection(collection);
|
|
}
|
|
}
|
|
else
|
|
removeCollection(collection);
|
|
|
|
if (disallowedStdTypes || !inserted || canEnable != types)
|
|
{
|
|
// Update the collection's status
|
|
AkonadiModel* model = static_cast<AkonadiModel*>(sourceModel());
|
|
if (!model->isCollectionBeingDeleted(collection.id()))
|
|
{
|
|
const QModelIndex ix = model->collectionIndex(collection);
|
|
if (!inserted || canEnable != types)
|
|
model->setData(ix, static_cast<int>(canEnable), AkonadiModel::EnabledTypesRole);
|
|
if (disallowedStdTypes)
|
|
model->setData(ix, static_cast<int>(stdTypes & ~disallowedStdTypes), AkonadiModel::IsStandardRole);
|
|
}
|
|
}
|
|
return canEnable;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Called when a collection parameter or status has changed.
|
|
* If it's the enabled status, add or remove the collection to/from the enabled
|
|
* list.
|
|
*/
|
|
void CollectionControlModel::statusChanged(const Collection& collection, AkonadiModel::Change change, const QVariant& value, bool inserted)
|
|
{
|
|
if (!collection.isValid())
|
|
return;
|
|
|
|
switch (change)
|
|
{
|
|
case AkonadiModel::Enabled:
|
|
{
|
|
const CalEvent::Types enabled = static_cast<CalEvent::Types>(value.toInt());
|
|
kDebug() << "id:" << collection.id() << ", enabled=" << enabled << ", inserted=" << inserted;
|
|
setEnabledStatus(collection, enabled, inserted);
|
|
break;
|
|
}
|
|
case AkonadiModel::ReadOnly:
|
|
{
|
|
bool readOnly = value.toBool();
|
|
kDebug() << "id:" << collection.id() << ", readOnly=" << readOnly;
|
|
if (readOnly)
|
|
{
|
|
// A read-only collection can't be the default for any alarm type
|
|
const CalEvent::Types std = standardTypes(collection, false);
|
|
if (std != CalEvent::EMPTY)
|
|
{
|
|
Collection c(collection);
|
|
setStandard(c, CalEvent::Types(CalEvent::EMPTY));
|
|
QWidget* messageParent = qobject_cast<QWidget*>(QObject::parent());
|
|
bool singleType = true;
|
|
QString msg;
|
|
switch (std)
|
|
{
|
|
case CalEvent::ACTIVE:
|
|
msg = i18nc("@info", "The calendar <resource>%1</resource> has been made read-only. "
|
|
"This was the default calendar for active alarms.",
|
|
collection.name());
|
|
break;
|
|
case CalEvent::ARCHIVED:
|
|
msg = i18nc("@info", "The calendar <resource>%1</resource> has been made read-only. "
|
|
"This was the default calendar for archived alarms.",
|
|
collection.name());
|
|
break;
|
|
case CalEvent::TEMPLATE:
|
|
msg = i18nc("@info", "The calendar <resource>%1</resource> has been made read-only. "
|
|
"This was the default calendar for alarm templates.",
|
|
collection.name());
|
|
break;
|
|
default:
|
|
msg = i18nc("@info", "<para>The calendar <resource>%1</resource> has been made read-only. "
|
|
"This was the default calendar for:%2</para>"
|
|
"<para>Please select new default calendars.</para>",
|
|
collection.name(), typeListForDisplay(std));
|
|
singleType = false;
|
|
break;
|
|
}
|
|
if (singleType)
|
|
msg = i18nc("@info", "<para>%1</para><para>Please select a new default calendar.</para>", msg);
|
|
KAMessageBox::information(messageParent, msg);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Check which alarm types can be enabled for a specified collection.
|
|
* If the collection uses the same backend storage as another collection, any
|
|
* alarm types already enabled in the other collection must be disabled in this
|
|
* collection. This is to avoid duplicating events between different resources,
|
|
* which causes user confusion and annoyance, and causes crashes.
|
|
* Parameters:
|
|
* collection - must be up to date (using AkonadiModel::refresh() etc.)
|
|
* collections = list of collections to search for duplicates.
|
|
* types = alarm types to be enabled for the collection.
|
|
* Reply = alarm types which can be enabled without duplicating other collections.
|
|
*/
|
|
CalEvent::Types CollectionControlModel::checkTypesToEnable(const Collection& collection, const Collection::List& collections, CalEvent::Types types)
|
|
{
|
|
types &= (CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE);
|
|
if (types)
|
|
{
|
|
// At least on alarm type is to be enabled
|
|
const KUrl location(collection.remoteId());
|
|
foreach (const Collection& c, collections)
|
|
{
|
|
if (c.id() != collection.id() && KUrl(c.remoteId()) == location)
|
|
{
|
|
// The collection duplicates the backend storage
|
|
// used by another enabled collection.
|
|
// N.B. don't refresh this collection - assume no change.
|
|
kDebug() << "Collection" << c.id() << "duplicates backend for" << collection.id();
|
|
if (c.hasAttribute<CollectionAttribute>())
|
|
{
|
|
types &= ~c.attribute<CollectionAttribute>()->enabled();
|
|
if (!types)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return types;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Create a bulleted list of alarm types for insertion into <para>...</para>.
|
|
*/
|
|
QString CollectionControlModel::typeListForDisplay(CalEvent::Types alarmTypes)
|
|
{
|
|
QString list;
|
|
if (alarmTypes & CalEvent::ACTIVE)
|
|
list += QLatin1String("<item>") + i18nc("@info/plain", "Active Alarms") + QLatin1String("</item>");
|
|
if (alarmTypes & CalEvent::ARCHIVED)
|
|
list += QLatin1String("<item>") + i18nc("@info/plain", "Archived Alarms") + QLatin1String("</item>");
|
|
if (alarmTypes & CalEvent::TEMPLATE)
|
|
list += QLatin1String("<item>") + i18nc("@info/plain", "Alarm Templates") + QLatin1String("</item>");
|
|
if (!list.isEmpty())
|
|
list = QLatin1String("<list>") + list + QLatin1String("</list>");
|
|
return list;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return whether a collection is both enabled and fully writable for a given
|
|
* alarm type.
|
|
* Optionally, the enabled status can be ignored.
|
|
* Reply: 1 = fully enabled and writable,
|
|
* 0 = enabled and writable except that backend calendar is in an old KAlarm format,
|
|
* -1 = not enabled, read-only, or incompatible format.
|
|
*/
|
|
int CollectionControlModel::isWritableEnabled(const Akonadi::Collection& collection, CalEvent::Type type)
|
|
{
|
|
KACalendar::Compat format;
|
|
return isWritableEnabled(collection, type, format);
|
|
}
|
|
int CollectionControlModel::isWritableEnabled(const Akonadi::Collection& collection, CalEvent::Type type, KACalendar::Compat& format)
|
|
{
|
|
int writable = AkonadiModel::isWritable(collection, format);
|
|
if (writable == -1)
|
|
return -1;
|
|
|
|
// Check the collection's enabled status
|
|
if (!instance()->collections().contains(collection)
|
|
|| !collection.hasAttribute<CollectionAttribute>())
|
|
return -1;
|
|
if (!collection.attribute<CollectionAttribute>()->isEnabled(type))
|
|
return -1;
|
|
return writable;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the standard collection for a specified mime type.
|
|
* If 'useDefault' is true and there is no standard collection, the only
|
|
* collection for the mime type will be returned as a default.
|
|
*/
|
|
Collection CollectionControlModel::getStandard(CalEvent::Type type, bool useDefault)
|
|
{
|
|
const QString mimeType = CalEvent::mimeType(type);
|
|
int defalt = -1;
|
|
Collection::List cols = instance()->collections();
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
AkonadiModel::instance()->refresh(cols[i]); // update with latest data
|
|
if (cols[i].isValid()
|
|
&& cols[i].contentMimeTypes().contains(mimeType))
|
|
{
|
|
if (cols[i].hasAttribute<CollectionAttribute>()
|
|
&& (cols[i].attribute<CollectionAttribute>()->standard() & type)
|
|
&& AkonadiModel::isCompatible(cols[i]))
|
|
return cols[i];
|
|
defalt = (defalt == -1) ? i : -2;
|
|
}
|
|
}
|
|
return (useDefault && defalt >= 0) ? cols[defalt] : Collection();
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return whether a collection is the standard collection for a specified
|
|
* mime type.
|
|
*/
|
|
bool CollectionControlModel::isStandard(Akonadi::Collection& collection, CalEvent::Type type)
|
|
{
|
|
if (!instance()->collections().contains(collection))
|
|
return false;
|
|
AkonadiModel::instance()->refresh(collection); // update with latest data
|
|
if (!collection.hasAttribute<CollectionAttribute>()
|
|
|| !AkonadiModel::isCompatible(collection))
|
|
return false;
|
|
return collection.attribute<CollectionAttribute>()->isStandard(type);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the alarm type(s) for which a collection is the standard collection.
|
|
*/
|
|
CalEvent::Types CollectionControlModel::standardTypes(const Collection& collection, bool useDefault)
|
|
{
|
|
if (!instance()->collections().contains(collection))
|
|
return CalEvent::EMPTY;
|
|
Collection col = collection;
|
|
AkonadiModel::instance()->refresh(col); // update with latest data
|
|
if (!AkonadiModel::isCompatible(col))
|
|
return CalEvent::EMPTY;
|
|
CalEvent::Types stdTypes = col.hasAttribute<CollectionAttribute>()
|
|
? col.attribute<CollectionAttribute>()->standard()
|
|
: CalEvent::EMPTY;
|
|
if (useDefault)
|
|
{
|
|
// Also return alarm types for which this is the only collection.
|
|
CalEvent::Types wantedTypes = AkonadiModel::types(collection) & ~stdTypes;
|
|
Collection::List cols = instance()->collections();
|
|
for (int i = 0, count = cols.count(); wantedTypes && i < count; ++i)
|
|
{
|
|
if (cols[i] == col)
|
|
continue;
|
|
AkonadiModel::instance()->refresh(cols[i]); // update with latest data
|
|
if (cols[i].isValid())
|
|
wantedTypes &= ~AkonadiModel::types(cols[i]);
|
|
}
|
|
stdTypes |= wantedTypes;
|
|
}
|
|
return stdTypes;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Set or clear a collection as the standard collection for a specified mime
|
|
* type. If it is being set as standard, the standard status for the mime type
|
|
* is cleared for all other collections.
|
|
*/
|
|
void CollectionControlModel::setStandard(Akonadi::Collection& collection, CalEvent::Type type, bool standard)
|
|
{
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
model->refresh(collection); // update with latest data
|
|
if (!AkonadiModel::isCompatible(collection))
|
|
standard = false; // the collection isn't writable
|
|
if (standard)
|
|
{
|
|
// The collection is being set as standard.
|
|
// Clear the 'standard' status for all other collections.
|
|
Collection::List cols = instance()->collections();
|
|
if (!cols.contains(collection))
|
|
return;
|
|
const CalEvent::Types ctypes = collection.hasAttribute<CollectionAttribute>()
|
|
? collection.attribute<CollectionAttribute>()->standard() : CalEvent::EMPTY;
|
|
if (ctypes & type)
|
|
return; // it's already the standard collection for this type
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
CalEvent::Types types;
|
|
if (cols[i] == collection)
|
|
{
|
|
cols[i] = collection; // update with latest data
|
|
types = ctypes | type;
|
|
}
|
|
else
|
|
{
|
|
model->refresh(cols[i]); // update with latest data
|
|
types = cols[i].hasAttribute<CollectionAttribute>()
|
|
? cols[i].attribute<CollectionAttribute>()->standard() : CalEvent::EMPTY;
|
|
if (!(types & type))
|
|
continue;
|
|
types &= ~type;
|
|
}
|
|
const QModelIndex index = model->collectionIndex(cols[i]);
|
|
model->setData(index, static_cast<int>(types), AkonadiModel::IsStandardRole);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The 'standard' status is being cleared for the collection.
|
|
// The collection doesn't have to be in this model's list of collections.
|
|
CalEvent::Types types = collection.hasAttribute<CollectionAttribute>()
|
|
? collection.attribute<CollectionAttribute>()->standard() : CalEvent::EMPTY;
|
|
if (types & type)
|
|
{
|
|
types &= ~type;
|
|
const QModelIndex index = model->collectionIndex(collection);
|
|
model->setData(index, static_cast<int>(types), AkonadiModel::IsStandardRole);
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Set which mime types a collection is the standard collection for.
|
|
* If it is being set as standard for any mime types, the standard status for
|
|
* those mime types is cleared for all other collections.
|
|
*/
|
|
void CollectionControlModel::setStandard(Akonadi::Collection& collection, CalEvent::Types types)
|
|
{
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
model->refresh(collection); // update with latest data
|
|
if (!AkonadiModel::isCompatible(collection))
|
|
types = CalEvent::EMPTY; // the collection isn't writable
|
|
if (types)
|
|
{
|
|
// The collection is being set as standard for at least one mime type.
|
|
// Clear the 'standard' status for all other collections.
|
|
Collection::List cols = instance()->collections();
|
|
if (!cols.contains(collection))
|
|
return;
|
|
const CalEvent::Types t = collection.hasAttribute<CollectionAttribute>()
|
|
? collection.attribute<CollectionAttribute>()->standard() : CalEvent::EMPTY;
|
|
if (t == types)
|
|
return; // there's no change to the collection's status
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
CalEvent::Types t;
|
|
if (cols[i] == collection)
|
|
{
|
|
cols[i] = collection; // update with latest data
|
|
t = types;
|
|
}
|
|
else
|
|
{
|
|
model->refresh(cols[i]); // update with latest data
|
|
t = cols[i].hasAttribute<CollectionAttribute>()
|
|
? cols[i].attribute<CollectionAttribute>()->standard() : CalEvent::EMPTY;
|
|
if (!(t & types))
|
|
continue;
|
|
t &= ~types;
|
|
}
|
|
const QModelIndex index = model->collectionIndex(cols[i]);
|
|
model->setData(index, static_cast<int>(t), AkonadiModel::IsStandardRole);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The 'standard' status is being cleared for the collection.
|
|
// The collection doesn't have to be in this model's list of collections.
|
|
if (collection.hasAttribute<CollectionAttribute>()
|
|
&& collection.attribute<CollectionAttribute>()->standard())
|
|
{
|
|
const QModelIndex index = model->collectionIndex(collection);
|
|
model->setData(index, static_cast<int>(types), AkonadiModel::IsStandardRole);
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Get the collection to use for storing an alarm.
|
|
* Optionally, the standard collection for the alarm type is returned. If more
|
|
* than one collection is a candidate, the user is prompted.
|
|
*/
|
|
Collection CollectionControlModel::destination(CalEvent::Type type, QWidget* promptParent, bool noPrompt, bool* cancelled)
|
|
{
|
|
if (cancelled)
|
|
*cancelled = false;
|
|
Collection standard;
|
|
if (type == CalEvent::EMPTY)
|
|
return standard;
|
|
standard = getStandard(type);
|
|
// Archived alarms are always saved in the default resource,
|
|
// else only prompt if necessary.
|
|
if (type == CalEvent::ARCHIVED || noPrompt || (!mAskDestination && standard.isValid()))
|
|
return standard;
|
|
|
|
// Prompt for which collection to use
|
|
CollectionListModel* model = new CollectionListModel(promptParent);
|
|
model->setFilterWritable(true);
|
|
model->setFilterEnabled(true);
|
|
model->setEventTypeFilter(type);
|
|
model->useCollectionColour(false);
|
|
Collection col;
|
|
switch (model->rowCount())
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
col = model->collection(0);
|
|
break;
|
|
default:
|
|
{
|
|
// Use AutoQPointer to guard against crash on application exit while
|
|
// the dialogue is still open. It prevents double deletion (both on
|
|
// deletion of 'promptParent', and on return from this function).
|
|
AutoQPointer<CollectionDialog> dlg = new CollectionDialog(model, promptParent);
|
|
dlg->setCaption(i18nc("@title:window", "Choose Calendar"));
|
|
dlg->setDefaultCollection(standard);
|
|
dlg->setMimeTypeFilter(QStringList(CalEvent::mimeType(type)));
|
|
if (dlg->exec())
|
|
col = dlg->selectedCollection();
|
|
if (!col.isValid() && cancelled)
|
|
*cancelled = true;
|
|
}
|
|
}
|
|
return col;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the enabled collections which contain a specified mime type.
|
|
* If 'writable' is true, only writable collections are included.
|
|
*/
|
|
Collection::List CollectionControlModel::enabledCollections(CalEvent::Type type, bool writable)
|
|
{
|
|
const QString mimeType = CalEvent::mimeType(type);
|
|
Collection::List cols = instance()->collections();
|
|
Collection::List result;
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
AkonadiModel::instance()->refresh(cols[i]); // update with latest data
|
|
if (cols[i].contentMimeTypes().contains(mimeType)
|
|
&& (!writable || ((cols[i].rights() & writableRights) == writableRights)))
|
|
result += cols[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the collection ID for a given resource ID.
|
|
*/
|
|
Collection CollectionControlModel::collectionForResource(const QString& resourceId)
|
|
{
|
|
const Collection::List cols = instance()->collections();
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
if (cols[i].resource() == resourceId)
|
|
return cols[i];
|
|
}
|
|
return Collection();
|
|
}
|
|
|
|
#if KDE_IS_VERSION(4,9,80)
|
|
/******************************************************************************
|
|
* Return whether all enabled collections have been populated.
|
|
*/
|
|
bool CollectionControlModel::isPopulated(Collection::Id colId)
|
|
{
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
Collection::List cols = instance()->collections();
|
|
for (int i = 0, count = cols.count(); i < count; ++i)
|
|
{
|
|
if ((colId == -1 || colId == cols[i].id())
|
|
&& !model->data(model->collectionIndex(cols[i].id()), AkonadiModel::IsPopulatedRole).toBool())
|
|
{
|
|
model->refresh(cols[i]); // update with latest data
|
|
if (!cols[i].hasAttribute<CollectionAttribute>()
|
|
|| cols[i].attribute<CollectionAttribute>()->enabled() == CalEvent::EMPTY)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Wait for one or all enabled collections to be populated.
|
|
* Reply = true if successful.
|
|
*/
|
|
bool CollectionControlModel::waitUntilPopulated(Collection::Id colId, int timeout)
|
|
{
|
|
kDebug();
|
|
int result = 1;
|
|
AkonadiModel* model = AkonadiModel::instance();
|
|
while (!model->isCollectionTreeFetched()
|
|
|| !isPopulated(colId))
|
|
{
|
|
if (!mPopulatedCheckLoop)
|
|
mPopulatedCheckLoop = new QEventLoop(this);
|
|
if (timeout > 0)
|
|
QTimer::singleShot(timeout * 1000, mPopulatedCheckLoop, SLOT(quit()));
|
|
result = mPopulatedCheckLoop->exec();
|
|
}
|
|
delete mPopulatedCheckLoop;
|
|
mPopulatedCheckLoop = 0;
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************
|
|
* Called when the Akonadi server has stopped. Reset the model.
|
|
*/
|
|
void CollectionControlModel::reset()
|
|
{
|
|
delete mPopulatedCheckLoop;
|
|
mPopulatedCheckLoop = 0;
|
|
|
|
// Clear the collections list. This is required because addCollection() or
|
|
// setCollections() don't work if the collections which they specify are
|
|
// already in the list.
|
|
setCollections(Collection::List());
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Exit from the populated event loop when a collection has been populated.
|
|
*/
|
|
void CollectionControlModel::collectionPopulated()
|
|
{
|
|
if (mPopulatedCheckLoop)
|
|
mPopulatedCheckLoop->exit(1);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the data for a given role, for a specified item.
|
|
*/
|
|
QVariant CollectionControlModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
return sourceModel()->data(mapToSource(index), role);
|
|
}
|
|
|
|
#include "collectionmodel.moc"
|
|
|
|
// vim: et sw=4:
|