kde-playground/kdepim/kalarm/resourcemodelview.cpp
2015-04-14 21:49:29 +00:00

542 lines
19 KiB
C++

/*
* resourcemodelview.cpp - model/view classes for alarm resource lists
* Program: kalarm
* Copyright © 2007-2011 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 "kalarm.h"
#include "messagebox.h"
#include "preferences.h"
#include "resourcemodelview.moc"
#include <klocale.h>
#include <kcolorutils.h>
#include <kdebug.h>
#include <QApplication>
#include <QToolTip>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QHelpEvent>
ResourceModel* ResourceModel::mInstance = 0;
ResourceModel* ResourceModel::instance(QObject* parent)
{
if (!mInstance)
mInstance = new ResourceModel(parent);
return mInstance;
}
ResourceModel::ResourceModel(QObject* parent)
: QAbstractListModel(parent)
{
refresh();
AlarmResources* resources = AlarmResources::instance();
connect(resources, SIGNAL(signalResourceModified(AlarmResource*)), SLOT(updateResource(AlarmResource*)));
connect(resources, SIGNAL(standardResourceChange(CalEvent::Type)), SLOT(slotStandardChanged(CalEvent::Type)));
connect(resources, SIGNAL(resourceStatusChanged(AlarmResource*,AlarmResources::Change)), SLOT(slotStatusChanged(AlarmResource*,AlarmResources::Change)));
connect(resources, SIGNAL(resourceLoaded(AlarmResource*,bool)), SLOT(slotLoaded(AlarmResource*,bool)));
}
int ResourceModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return mResources.count();
}
QModelIndex ResourceModel::index(int row, int column, const QModelIndex& parent) const
{
if (parent.isValid() || row >= mResources.count())
return QModelIndex();
return createIndex(row, column, mResources[row]);
}
QVariant ResourceModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
AlarmResource* resource = static_cast<AlarmResource*>(index.internalPointer());
if (!resource)
return QVariant();
switch (role)
{
case Qt::DisplayRole:
return resource->resourceName();
case Qt::CheckStateRole:
return resource->isEnabled() ? Qt::Checked : Qt::Unchecked;
case Qt::ForegroundRole:
{
QColor colour;
switch (resource->alarmType())
{
case CalEvent::ACTIVE: colour = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color(); break;
case CalEvent::ARCHIVED: colour = Preferences::archivedColour(); break;
case CalEvent::TEMPLATE: colour = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color(); break;
default: break;
}
if (colour.isValid())
return resource->readOnly() ? KColorUtils::lighten(colour, 0.25) : colour;
break;
}
case Qt::BackgroundRole:
if (resource->colour().isValid())
return resource->colour();
break;
case Qt::FontRole:
{
if (!resource->isEnabled() || !resource->standardResource())
break;
QFont font = mFont;
font.setBold(true);
return font;
}
case Qt::ToolTipRole:
{
QString name = '@' + resource->resourceName(); // insert markers for stripping out name
QString type = '@' + resource->displayType();
bool inactive = !resource->isActive();
QString disabled = resource->isWrongAlarmType() ? i18nc("@info/plain", "Disabled (wrong alarm type)") : i18nc("@info/plain", "Disabled");
QString readonly = i18nc("@info/plain", "Read-only");
if (inactive && resource->readOnly())
return i18nc("@info:tooltip",
"%1"
"<nl/>%2: <filename>%3</filename>"
"<nl/>%4, %5",
name, type, resource->displayLocation(), disabled, readonly);
if (inactive || resource->readOnly())
return i18nc("@info:tooltip",
"%1"
"<nl/>%2: <filename>%3</filename>"
"<nl/>%4",
name, type, resource->displayLocation(),
(inactive ? disabled : readonly));
return i18nc("@info:tooltip",
"%1"
"<nl/>%2: <filename>%3</filename>",
name, type, resource->displayLocation());
}
default:
break;
}
return QVariant();
}
/******************************************************************************
* Set the font to use for all items, or the checked state of one item.
* The font must always be set at initialisation.
*/
bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
mErrorPrompt.clear();
if (role == Qt::FontRole)
{
// Set the font used in all views.
// This enables data(index, Qt::FontRole) to return bold when appropriate.
mFont = value.value<QFont>();
return true;
}
if (role != Qt::CheckStateRole || !index.isValid())
return false;
AlarmResource* resource = static_cast<AlarmResource*>(index.internalPointer());
if (!resource)
return false;
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
bool active = (state == Qt::Checked);
bool saveChange = false;
AlarmResources* resources = AlarmResources::instance();
if (active)
{
// Enable the resource
resource->setActive(true); // enable it now so that load() will work
saveChange = resources->load(resource);
resource->setActive(false); // reset so that setEnabled() will work
}
else
{
// Disable the resource
saveChange = resource->saveAndClose(); // close resource after it is saved
}
if (saveChange)
resource->setEnabled(active);
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags ResourceModel::flags(const QModelIndex&) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
}
/******************************************************************************
* Return the resource referred to by an index.
*/
AlarmResource* ResourceModel::resource(const QModelIndex& index) const
{
if (!index.isValid())
return 0;
return static_cast<AlarmResource*>(index.internalPointer());
}
/******************************************************************************
* Emit a signal that a resource has changed.
*/
void ResourceModel::notifyChange(const QModelIndex& index)
{
if (index.isValid())
emit dataChanged(index, index);
}
/******************************************************************************
* Reload the resources list.
*/
void ResourceModel::refresh()
{
// This would be better done by a reset(), but the signals are private to QAbstractItemModel
if (!mResources.isEmpty())
{
beginRemoveRows(QModelIndex(), 0, mResources.count() - 1);
mResources.clear();
endRemoveRows();
}
QList<AlarmResource*> newResources;
AlarmResourceManager* manager = AlarmResources::instance()->resourceManager();
for (AlarmResourceManager::Iterator it = manager->begin(); it != manager->end(); ++it)
newResources += *it;
if (!newResources.isEmpty())
{
beginInsertRows(QModelIndex(), 0, newResources.count() - 1);
mResources = newResources;
endInsertRows();
}
}
/******************************************************************************
* Add the specified resource to the list.
*/
void ResourceModel::addResource(AlarmResource* resource)
{
int row = mResources.count();
beginInsertRows(QModelIndex(), row, row);
mResources += resource;
endInsertRows();
}
/******************************************************************************
* Delete the specified resource from the list.
*/
void ResourceModel::removeResource(AlarmResource* resource)
{
int row = mResources.indexOf(resource);
if (row >= 0)
{
beginRemoveRows(QModelIndex(), row, row);
mResources.removeAt(row);
endRemoveRows();
}
}
/******************************************************************************
* Called when the resource has been updated , to update the
* active status displayed for the resource item.
*/
void ResourceModel::updateResource(AlarmResource* resource)
{
int row = mResources.indexOf(resource);
if (row >= 0)
{
QModelIndex ix = index(row, 0, QModelIndex());
emit dataChanged(ix, ix);
}
}
/******************************************************************************
* Called when a different resource has been set as the standard resource.
*/
void ResourceModel::slotStandardChanged(CalEvent::Type type)
{
for (int row = 0, end = mResources.count(); row < end; ++row)
{
if (mResources[row]->alarmType() == type)
{
QModelIndex ix = index(row, 0, QModelIndex());
emit dataChanged(ix, ix);
}
}
}
/******************************************************************************
* Called when a resource has completed loading.
* Check in case its status has changed.
*/
void ResourceModel::slotLoaded(AlarmResource* resource, bool active)
{
if (active)
updateResource(resource);
}
/******************************************************************************
* Called when a resource status has changed, to update the list.
*/
void ResourceModel::slotStatusChanged(AlarmResource* resource, AlarmResources::Change change)
{
switch (change)
{
case AlarmResources::Added:
addResource(resource);
break;
case AlarmResources::Enabled:
case AlarmResources::ReadOnly:
case AlarmResources::Colour:
updateResource(resource);
break;
default:
break;
}
}
/*=============================================================================
= Class: ResourceFilterModel
= Proxy model for filtering resource lists.
=============================================================================*/
ResourceFilterModel::ResourceFilterModel(QAbstractItemModel* baseModel, QObject* parent)
: QSortFilterProxyModel(parent),
mResourceType(CalEvent::EMPTY)
{
setSourceModel(baseModel);
}
void ResourceFilterModel::setFilter(CalEvent::Type type)
{
if (type != mResourceType)
{
mResourceType = type;
invalidateFilter();
}
}
bool ResourceFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex&) const
{
return static_cast<ResourceModel*>(sourceModel())->resource(sourceModel()->index(sourceRow, 0))->alarmType() == mResourceType;
}
/******************************************************************************
* Return the resource referred to by an index.
*/
AlarmResource* ResourceFilterModel::resource(int row) const
{
return static_cast<ResourceModel*>(sourceModel())->resource(mapToSource(index(row, 0)));
}
AlarmResource* ResourceFilterModel::resource(const QModelIndex& index) const
{
return static_cast<ResourceModel*>(sourceModel())->resource(mapToSource(index));
}
/******************************************************************************
* Emit a signal that a resource has changed.
*/
void ResourceFilterModel::notifyChange(int row)
{
static_cast<ResourceModel*>(sourceModel())->notifyChange(mapToSource(index(row, 0)));
}
void ResourceFilterModel::notifyChange(const QModelIndex& index)
{
static_cast<ResourceModel*>(sourceModel())->notifyChange(mapToSource(index));
}
/*=============================================================================
= Class: ResourceDelegate
= Model/view delegate for resource list.
=============================================================================*/
/******************************************************************************
* Process a change of state of the checkbox for a resource.
*/
bool ResourceDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
if (!(model->flags(index) & Qt::ItemIsEnabled))
return false;
if (event->type() == QEvent::MouseButtonRelease
|| event->type() == QEvent::MouseButtonDblClick)
{
const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
QRect checkRect = QStyle::alignedRect(option.direction, Qt::AlignLeft | Qt::AlignVCenter,
check(option, option.rect, Qt::Checked).size(),
QRect(option.rect.x() + textMargin, option.rect.y(), option.rect.width(), option.rect.height()));
if (!checkRect.contains(static_cast<QMouseEvent*>(event)->pos()))
return false;
if (event->type() == QEvent::MouseButtonDblClick)
return true; // ignore double clicks
}
else if (event->type() == QEvent::KeyPress)
{
if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space
&& static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
return false;
}
else
return false;
QVariant value = index.data(Qt::CheckStateRole);
if (!value.isValid())
return false;
Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
if (state == Qt::Unchecked)
{
// The resource is to be disabled.
// Check for eligibility.
AlarmResource* resource = static_cast<ResourceFilterModel*>(model)->resource(index);
if (!resource)
return false;
if (resource->standardResource())
{
// It's the standard resource for its type.
if (resource->alarmType() == CalEvent::ACTIVE)
{
KAMessageBox::sorry(static_cast<QWidget*>(parent()),
i18nc("@info", "You cannot disable your default active alarm calendar."));
return false;
}
if (resource->alarmType() == CalEvent::ARCHIVED && Preferences::archivedKeepDays())
{
// Only allow the archived alarms standard resource to be disabled if
// we're not saving archived alarms.
KAMessageBox::sorry(static_cast<QWidget*>(parent()),
i18nc("@info", "You cannot disable your default archived alarm calendar "
"while expired alarms are configured to be kept."));
return false;
}
if (KAMessageBox::warningContinueCancel(static_cast<QWidget*>(parent()),
i18nc("@info", "Do you really want to disable your default calendar?"))
== KMessageBox::Cancel)
return false;
}
}
return model->setData(index, state, Qt::CheckStateRole);
}
/*=============================================================================
= Class: ResourceView
= View displaying a list of resources.
=============================================================================*/
void ResourceView::setModel(QAbstractItemModel* model)
{
model->setData(QModelIndex(), viewOptions().font, Qt::FontRole);
QListView::setModel(model);
setItemDelegate(new ResourceDelegate(this));
}
/******************************************************************************
* Return the resource for a given row.
*/
AlarmResource* ResourceView::resource(int row) const
{
return static_cast<ResourceFilterModel*>(model())->resource(row);
}
AlarmResource* ResourceView::resource(const QModelIndex& index) const
{
return static_cast<ResourceFilterModel*>(model())->resource(index);
}
/******************************************************************************
* Emit a signal that a resource has changed.
*/
void ResourceView::notifyChange(int row) const
{
static_cast<ResourceFilterModel*>(model())->notifyChange(row);
}
void ResourceView::notifyChange(const QModelIndex& index) const
{
static_cast<ResourceFilterModel*>(model())->notifyChange(index);
}
/******************************************************************************
* Called when a mouse button is released.
* Any currently selected resource is deselected.
*/
void ResourceView::mouseReleaseEvent(QMouseEvent* e)
{
if (!indexAt(e->pos()).isValid())
clearSelection();
QListView::mouseReleaseEvent(e);
}
/******************************************************************************
* Called when a ToolTip or WhatsThis event occurs.
*/
bool ResourceView::viewportEvent(QEvent* e)
{
if (e->type() == QEvent::ToolTip && isActiveWindow())
{
QHelpEvent* he = static_cast<QHelpEvent*>(e);
QModelIndex index = indexAt(he->pos());
QVariant value = model()->data(index, Qt::ToolTipRole);
if (qVariantCanConvert<QString>(value))
{
QString toolTip = value.toString();
int i = toolTip.indexOf('@');
if (i > 0)
{
int j = toolTip.indexOf(QRegExp("<(nl|br)", Qt::CaseInsensitive), i + 1);
int k = toolTip.indexOf('@', j);
QString name = toolTip.mid(i + 1, j - i - 1);
value = model()->data(index, Qt::FontRole);
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 resource 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);
}
// vim: et sw=4: