/*
 *  resourceselector.cpp  -  calendar resource selection widget
 *  Program:  kalarm
 *  Copyright © 2006-2013 by David Jarvie <djarvie@kde.org>
 *  Based on KOrganizer's ResourceView class and KAddressBook's ResourceSelection class,
 *  Copyright (C) 2003,2004 Cornelius Schumacher <schumacher@kde.org>
 *  Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
 *  Copyright (c) 2004 Tobias Koenig <tokoe@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 "resourceselector.h"

#include "kalarm.h"
#include "alarmcalendar.h"
#include "autoqpointer.h"
#ifdef USE_AKONADI
#include "akonadiresourcecreator.h"
#include "calendarmigrator.h"
#include "kalarmapp.h"
#else
#include "alarmresources.h"
#include "eventlistmodel.h"
#include "resourcemodelview.h"
#endif
#include "messagebox.h"
#include "packedlayout.h"
#include "preferences.h"
#include "resourceconfigdialog.h"

#ifdef USE_AKONADI
#include <akonadi/agentmanager.h>
#include <akonadi/agentinstancecreatejob.h>
#include <akonadi/agenttype.h>
#include <akonadi/collectionpropertiesdialog.h>
#include <akonadi/entitydisplayattribute.h>
#else
#include <kcal/resourcecalendar.h>
#endif

#include <kdialog.h>
#include <klocale.h>
#include <kglobal.h>
#include <kcombobox.h>
#include <kinputdialog.h>
#include <kmenu.h>
#include <kdebug.h>
#include <kicon.h>
#include <kactioncollection.h>
#include <kaction.h>
#include <ktoggleaction.h>
#include <kcolordialog.h>

#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QResizeEvent>
#include <QApplication>

#ifdef USE_AKONADI
using namespace KCalCore;
#else
using namespace KCal;
#endif
#ifdef USE_AKONADI
using namespace Akonadi;
#endif


#ifdef USE_AKONADI
ResourceSelector::ResourceSelector(QWidget* parent)
#else
ResourceSelector::ResourceSelector(AlarmResources* calendar, QWidget* parent)
#endif
    : QFrame(parent),
#ifndef USE_AKONADI
      mCalendar(calendar),
#endif
      mContextMenu(0),
      mActionReload(0),
      mActionShowDetails(0),
      mActionSetColour(0),
      mActionClearColour(0),
      mActionEdit(0),
#ifdef USE_AKONADI
      mActionUpdate(0),
#else
      mActionSave(0),
#endif
      mActionRemove(0),
      mActionImport(0),
      mActionExport(0),
      mActionSetDefault(0)
{
    QBoxLayout* topLayout = new QVBoxLayout(this);
    topLayout->setMargin(KDialog::spacingHint());   // use spacingHint for the margin

    QLabel* label = new QLabel(i18nc("@title:group", "Calendars"), this);
    topLayout->addWidget(label, 0, Qt::AlignHCenter);

    mAlarmType = new KComboBox(this);
    mAlarmType->addItem(i18nc("@item:inlistbox", "Active Alarms"));
    mAlarmType->addItem(i18nc("@item:inlistbox", "Archived Alarms"));
    mAlarmType->addItem(i18nc("@item:inlistbox", "Alarm Templates"));
    mAlarmType->setFixedHeight(mAlarmType->sizeHint().height());
    mAlarmType->setWhatsThis(i18nc("@info:whatsthis", "Choose which type of data to show alarm calendars for"));
    topLayout->addWidget(mAlarmType);
    // No spacing between combo box and listview.

#ifdef USE_AKONADI
    CollectionFilterCheckListModel* model = new CollectionFilterCheckListModel(this);
    mListView = new CollectionView(model, this);
#else
    ResourceModel* model = ResourceModel::instance();
    ResourceFilterModel* filterModel = new ResourceFilterModel(model, this);
    mListView = new ResourceView(this);
    mListView->setModel(filterModel);
#endif
    connect(mListView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(selectionChanged()));
    mListView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(mListView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(contextMenuRequested(QPoint)));
    mListView->setWhatsThis(i18nc("@info:whatsthis",
                                  "List of available calendars of the selected type. The checked state shows whether a calendar "
                                 "is enabled (checked) or disabled (unchecked). The default calendar is shown in bold."));
    topLayout->addWidget(mListView, 1);
    topLayout->addSpacing(KDialog::spacingHint());

    PackedLayout* blayout = new PackedLayout(Qt::AlignHCenter);
    blayout->setMargin(0);
    blayout->setSpacing(KDialog::spacingHint());
    topLayout->addLayout(blayout);

    mAddButton    = new QPushButton(i18nc("@action:button", "Add..."), this);
    mEditButton   = new QPushButton(i18nc("@action:button", "Edit..."), this);
    mDeleteButton = new QPushButton(i18nc("@action:button", "Remove"), this);
    blayout->addWidget(mAddButton);
    blayout->addWidget(mEditButton);
    blayout->addWidget(mDeleteButton);
    mEditButton->setWhatsThis(i18nc("@info:whatsthis", "Edit the highlighted calendar"));
    mDeleteButton->setWhatsThis(i18nc("@info:whatsthis", "<para>Remove the highlighted calendar from the list.</para>"
                                     "<para>The calendar itself is left intact, and may subsequently be reinstated in the list if desired.</para>"));
    mEditButton->setDisabled(true);
    mDeleteButton->setDisabled(true);
    connect(mAddButton, SIGNAL(clicked()), SLOT(addResource()));
    connect(mEditButton, SIGNAL(clicked()), SLOT(editResource()));
    connect(mDeleteButton, SIGNAL(clicked()), SLOT(removeResource()));

#ifdef USE_AKONADI
    connect(AkonadiModel::instance(), SIGNAL(collectionAdded(Akonadi::Collection)),
                                      SLOT(slotCollectionAdded(Akonadi::Collection)));
#else
    connect(mCalendar, SIGNAL(resourceStatusChanged(AlarmResource*,AlarmResources::Change)), SLOT(slotStatusChanged(AlarmResource*,AlarmResources::Change)));
#endif

    connect(mAlarmType, SIGNAL(activated(int)), SLOT(alarmTypeSelected()));
    QTimer::singleShot(0, this, SLOT(alarmTypeSelected()));

    Preferences::connect(SIGNAL(archivedKeepDaysChanged(int)), this, SLOT(archiveDaysChanged(int)));
}

/******************************************************************************
* Called when an alarm type has been selected.
* Filter the resource list to show resources of the selected alarm type, and
* add appropriate whatsThis texts to the list and to the Add button.
*/
void ResourceSelector::alarmTypeSelected()
{
    QString addTip;
    switch (mAlarmType->currentIndex())
    {
        case 0:
            mCurrentAlarmType = CalEvent::ACTIVE;
            addTip = i18nc("@info:tooltip", "Add a new active alarm calendar");
            break;
        case 1:
            mCurrentAlarmType = CalEvent::ARCHIVED;
            addTip = i18nc("@info:tooltip", "Add a new archived alarm calendar");
            break;
        case 2:
            mCurrentAlarmType = CalEvent::TEMPLATE;
            addTip = i18nc("@info:tooltip", "Add a new alarm template calendar");
            break;
    }
    // WORKAROUND: Switch scroll bars off to avoid crash (see explanation
    // in reinstateAlarmTypeScrollBars() description).
    mListView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    mListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
#ifdef USE_AKONADI
    mListView->collectionModel()->setEventTypeFilter(mCurrentAlarmType);
#else
    static_cast<ResourceFilterModel*>(mListView->model())->setFilter(mCurrentAlarmType);
#endif
    mAddButton->setWhatsThis(addTip);
    mAddButton->setToolTip(addTip);
    // WORKAROUND: Switch scroll bars back on after allowing geometry to update ...
    QTimer::singleShot(0, this, SLOT(reinstateAlarmTypeScrollBars()));

#ifdef USE_AKONADI
    selectionChanged();   // enable/disable buttons
#endif
}

/******************************************************************************
* WORKAROUND for crash due to presumed Qt bug.
* Switch scroll bars off. This is to avoid a crash which can very occasionally
* happen when changing from a list of calendars which requires vertical scroll
* bars, to a list whose text is very slightly wider but which doesn't require
* scroll bars at all. (The suspicion is that the width is such that it would
* require horizontal scroll bars if the vertical scroll bars were still
* present.) Presumably due to a Qt bug, this can result in a recursive call to
* ResourceView::viewportEvent() with a Resize event.
*
* The crash only occurs if the ResourceSelector happens to have exactly (within
* one pixel) the "right" width to create the crash.
*/
void ResourceSelector::reinstateAlarmTypeScrollBars()
{
    mListView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    mListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}

/******************************************************************************
* Prompt the user for a new resource to add to the list.
*/
void ResourceSelector::addResource()
{
#ifdef USE_AKONADI
    AkonadiResourceCreator* creator = new AkonadiResourceCreator(mCurrentAlarmType, this);
    connect(creator, SIGNAL(finished(AkonadiResourceCreator*,bool)), 
                     SLOT(resourceAdded(AkonadiResourceCreator*,bool)));
    creator->createResource();
#else
    AlarmResourceManager* manager = mCalendar->resourceManager();
    QStringList descs = manager->resourceTypeDescriptions();
    bool ok = false;
    QString desc = KInputDialog::getItem(i18nc("@title:window", "Calendar Configuration"),
                                         i18nc("@info", "Select storage type of new calendar:"), descs, 0, false, &ok, this);
    if (!ok  ||  descs.isEmpty())
        return;
    QString type = manager->resourceTypeNames()[descs.indexOf(desc)];
    AlarmResource* resource = dynamic_cast<AlarmResource*>(manager->createResource(type));
    if (!resource)
    {
        KAMessageBox::error(this, i18nc("@info", "Unable to create calendar of type <resource>%1</resource>.", type));
        return;
    }
    resource->setResourceName(i18nc("@info/plain", "%1 calendar", type));
    resource->setAlarmType(mCurrentAlarmType);
    resource->setActive(false);   // prevent setReadOnly() declaring it as unwritable before we've tried to load it

    // Use AutoQPointer to guard against crash on application exit while
    // the dialogue is still open. It prevents double deletion (both on
    // deletion of ResourceSelector, and on return from this function).
    AutoQPointer<ResourceConfigDialog> dlg = new ResourceConfigDialog(this, resource);
    if (dlg->exec() == QDialog::Accepted)
    {
        resource->setEnabled(true);
        resource->setTimeSpec(Preferences::timeZone());
        manager->add(resource);
        manager->writeConfig();
        mCalendar->resourceAdded(resource);   // load the resource and connect in-process change signals
    }
    else
    {
        delete resource;
        resource = 0;
    }
#endif
}

#ifdef USE_AKONADI
/******************************************************************************
* Called when the job started by AkonadiModel::addCollection() has completed.
*/
void ResourceSelector::resourceAdded(AkonadiResourceCreator* creator, bool success)
{
    if (success)
    {
        AgentInstance agent = creator->agentInstance();
        if (agent.isValid())
        {
            // Note that we're expecting the agent's Collection to be added
            mAddAgents += agent;
        }
    }
    delete creator;
}

/******************************************************************************
* Called when a collection is added to the AkonadiModel.
*/
void ResourceSelector::slotCollectionAdded(const Collection& collection)
{
    if (collection.isValid())
    {
        AgentInstance agent = AgentManager::self()->instance(collection.resource());
        if (agent.isValid())
        {
            int i = mAddAgents.indexOf(agent);
            if (i >= 0)
            {
                // The collection belongs to an agent created by addResource()
                CalEvent::Types types = CalEvent::types(collection.contentMimeTypes());
                CollectionControlModel::setEnabled(collection, types, true);
                if (!(types & mCurrentAlarmType))
                {
                    // The user has selected alarm types for the resource
                    // which don't include the currently displayed type.
                    // Show a collection list which includes a selected type.
                    int index = -1;
                    if (types & CalEvent::ACTIVE)
                        index = 0;
                    else if (types & CalEvent::ARCHIVED)
                        index = 1;
                    else if (types & CalEvent::TEMPLATE)
                        index = 2;
                    if (index >= 0)
                    {
                        mAlarmType->setCurrentIndex(index);
                        alarmTypeSelected();
                    }
                }
                mAddAgents.removeAt(i);
            }
        }
    }
}
#endif

/******************************************************************************
* Edit the currently selected resource.
*/
void ResourceSelector::editResource()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
    {
        AgentInstance instance = AgentManager::self()->instance(collection.resource());
        if (instance.isValid())
            instance.configure(this);
    }
#else
    AlarmResource* resource = currentResource();
    if (!resource)
        return;
    bool readOnly = resource->readOnly();
    // Use AutoQPointer to guard against crash on application exit while
    // the dialogue is still open. It prevents double deletion (both on
    // deletion of ResourceSelector, and on return from this function).
    AutoQPointer<ResourceConfigDialog> dlg = new ResourceConfigDialog(this, resource);
    if (dlg->exec() == QDialog::Accepted)
    {
        // Act on any changed settings.
        // Read-only is handled automatically by AlarmResource::setReadOnly().
        if (!readOnly  &&  resource->readOnly()  &&  resource->standardResource())
        {
            // A standard resource is being made read-only.
            if (resource->alarmType() == CalEvent::ACTIVE)
            {
                KAMessageBox::sorry(this, i18nc("@info", "You cannot make your default active alarm calendar read-only."));
                resource->setReadOnly(false);
            }
            else if (resource->alarmType() == CalEvent::ARCHIVED  &&  Preferences::archivedKeepDays())
            {
                // Only allow the archived alarms standard resource to be made read-only
                // if we're not saving archived alarms.
                KAMessageBox::sorry(this, i18nc("@info", "You cannot make your default archived alarm calendar "
                                                "read-only while expired alarms are configured to be kept."));
                resource->setReadOnly(false);
            }
            else if (KAMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to make your default calendar read-only?"))
                       == KMessageBox::Cancel)
            {
                resource->setReadOnly(false);
            }
        }
    }
#endif
}

#ifdef USE_AKONADI
/******************************************************************************
* Update the backend storage format for the currently selected resource in the
* displayed list.
*/
void ResourceSelector::updateResource()
{
    Collection collection = currentResource();
    if (!collection.isValid())
        return;
    AkonadiModel::instance()->refresh(collection);  // update with latest data
    CalendarMigrator::updateToCurrentFormat(collection, true, this);
}
#endif

/******************************************************************************
* Remove the currently selected resource from the displayed list.
*/
void ResourceSelector::removeResource()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (!collection.isValid())
        return;
    QString name = collection.name();
    // Check if it's the standard or only resource for at least one type.
    CalEvent::Types allTypes      = AkonadiModel::types(collection);
    CalEvent::Types standardTypes = CollectionControlModel::standardTypes(collection, true);
    CalEvent::Type  currentType   = currentResourceType();
    CalEvent::Type stdType = (standardTypes & CalEvent::ACTIVE)   ? CalEvent::ACTIVE
                                   : (standardTypes & CalEvent::ARCHIVED) ? CalEvent::ARCHIVED
                                   : CalEvent::EMPTY;
#else
    AlarmResource* resource = currentResource();
    if (!resource)
        return;
    QString name = resource->resourceName();
    bool std = resource->standardResource();
    // Check if it's the standard resource for its type.
    CalEvent::Type stdType = std ? resource->alarmType() : CalEvent::EMPTY;
#endif
    if (stdType == CalEvent::ACTIVE)
    {
        KAMessageBox::sorry(this, i18nc("@info", "You cannot remove your default active alarm calendar."));
        return;
    }
    if (stdType == CalEvent::ARCHIVED  &&  Preferences::archivedKeepDays())
    {
        // Only allow the archived alarms standard resource to be removed if
        // we're not saving archived alarms.
        KAMessageBox::sorry(this, i18nc("@info", "You cannot remove your default archived alarm calendar "
                                        "while expired alarms are configured to be kept."));
        return;
    }
#ifdef USE_AKONADI
    QString text;
    if (standardTypes)
    {
        // It's a standard resource for at least one alarm type
        if (allTypes != currentType)
        {
            // It also contains alarm types other than the currently displayed type
            QString stdTypes = CollectionControlModel::typeListForDisplay(standardTypes);
            QString otherTypes;
            CalEvent::Types nonStandardTypes(allTypes & ~standardTypes);
            if (nonStandardTypes != currentType)
                otherTypes = i18nc("@info", "<para>It also contains:%1</para>", CollectionControlModel::typeListForDisplay(nonStandardTypes));
            text = i18nc("@info", "<para><resource>%1</resource> is the default calendar for:%2</para>%3"
                                  "<para>Do you really want to remove it from all calendar lists?</para>", name, stdTypes, otherTypes);
        }
        else
            text = i18nc("@info", "Do you really want to remove your default calendar (<resource>%1</resource>) from the list?", name);
    }
    else if (allTypes != currentType)
        text = i18nc("@info", "<para><resource>%1</resource> contains:%2</para><para>Do you really want to remove it from all calendar lists?</para>",
                     name, CollectionControlModel::typeListForDisplay(allTypes));
    else
        text = i18nc("@info", "Do you really want to remove the calendar <resource>%1</resource> from the list?", name);
#else
    QString text = std ? i18nc("@info", "Do you really want to remove your default calendar (<resource>%1</resource>) from the list?", name)
                       : i18nc("@info", "Do you really want to remove the calendar <resource>%1</resource> from the list?", name);
#endif
    if (KAMessageBox::warningContinueCancel(this, text, QString(), KStandardGuiItem::remove()) == KMessageBox::Cancel)
        return;

#ifdef USE_AKONADI
    AkonadiModel::instance()->removeCollection(collection);
#else
    // Remove resource from alarm and resource lists before deleting it, to avoid
    // crashes when display updates occur immediately after it is deleted.
    if (resource->alarmType() == CalEvent::TEMPLATE)
        EventListModel::templates()->removeResource(resource);
    else
        EventListModel::alarms()->removeResource(resource);
    ResourceModel::instance()->removeResource(resource);
    AlarmResourceManager* manager = mCalendar->resourceManager();
    manager->remove(resource);
    manager->writeConfig();
#endif
}

/******************************************************************************
* Called when the current selection changes, to enable/disable the
* Delete and Edit buttons accordingly.
*/
void ResourceSelector::selectionChanged()
{
    bool state = mListView->selectionModel()->selectedRows().count();
    mDeleteButton->setEnabled(state);
    mEditButton->setEnabled(state);
}

/******************************************************************************
* Initialise the button and context menu actions.
*/
void ResourceSelector::initActions(KActionCollection* actions)
{
    mActionReload      = new KAction(KIcon(QLatin1String("view-refresh")), i18nc("@action Reload calendar", "Re&load"), this);
    actions->addAction(QLatin1String("resReload"), mActionReload);
    connect(mActionReload, SIGNAL(triggered(bool)), SLOT(reloadResource()));
#ifndef USE_AKONADI
    mActionSave        = new KAction(KIcon(QLatin1String("document-save")), i18nc("@action", "&Save"), this);
    actions->addAction(QLatin1String("resSave"), mActionSave);
    connect(mActionSave, SIGNAL(triggered(bool)), SLOT(saveResource()));
#endif
    mActionShowDetails = new KAction(KIcon(QLatin1String("help-about")), i18nc("@action", "Show &Details"), this);
    actions->addAction(QLatin1String("resDetails"), mActionShowDetails);
    connect(mActionShowDetails, SIGNAL(triggered(bool)), SLOT(showInfo()));
    mActionSetColour   = new KAction(KIcon(QLatin1String("color-picker")), i18nc("@action", "Set &Color..."), this);
    actions->addAction(QLatin1String("resSetColour"), mActionSetColour);
    connect(mActionSetColour, SIGNAL(triggered(bool)), SLOT(setColour()));
    mActionClearColour   = new KAction(i18nc("@action", "Clear C&olor"), this);
    actions->addAction(QLatin1String("resClearColour"), mActionClearColour);
    connect(mActionClearColour, SIGNAL(triggered(bool)), SLOT(clearColour()));
    mActionEdit        = new KAction(KIcon(QLatin1String("document-properties")), i18nc("@action", "&Edit..."), this);
    actions->addAction(QLatin1String("resEdit"), mActionEdit);
    connect(mActionEdit, SIGNAL(triggered(bool)), SLOT(editResource()));
#ifdef USE_AKONADI
    mActionUpdate      = new KAction(i18nc("@action", "&Update Calendar Format"), this);
    actions->addAction(QLatin1String("resUpdate"), mActionUpdate);
    connect(mActionUpdate, SIGNAL(triggered(bool)), SLOT(updateResource()));
#endif
    mActionRemove      = new KAction(KIcon(QLatin1String("edit-delete")), i18nc("@action", "&Remove"), this);
    actions->addAction(QLatin1String("resRemove"), mActionRemove);
    connect(mActionRemove, SIGNAL(triggered(bool)), SLOT(removeResource()));
    mActionSetDefault  = new KToggleAction(this);
    actions->addAction(QLatin1String("resDefault"), mActionSetDefault);
    connect(mActionSetDefault, SIGNAL(triggered(bool)), SLOT(setStandard()));
    QAction* action    = new KAction(KIcon(QLatin1String("document-new")), i18nc("@action", "&Add..."), this);
    actions->addAction(QLatin1String("resAdd"), action);
    connect(action, SIGNAL(triggered(bool)), SLOT(addResource()));
    mActionImport      = new KAction(i18nc("@action", "Im&port..."), this);
    actions->addAction(QLatin1String("resImport"), mActionImport);
    connect(mActionImport, SIGNAL(triggered(bool)), SLOT(importCalendar()));
    mActionExport      = new KAction(i18nc("@action", "E&xport..."), this);
    actions->addAction(QLatin1String("resExport"), mActionExport);
    connect(mActionExport, SIGNAL(triggered(bool)), SLOT(exportCalendar()));
}

void ResourceSelector::setContextMenu(KMenu* menu)
{
    mContextMenu = menu;
}

/******************************************************************************
* Display the context menu for the selected calendar.
*/
void ResourceSelector::contextMenuRequested(const QPoint& viewportPos)
{
    if (!mContextMenu)
        return;
    bool active    = false;
    bool writable  = false;
#ifdef USE_AKONADI
    bool updatable = false;
    Collection collection;
#else
    AlarmResource* resource = 0;
#endif
    if (mListView->selectionModel()->hasSelection())
    {
        QModelIndex index = mListView->indexAt(viewportPos);
        if (index.isValid())
#ifdef USE_AKONADI
            collection = mListView->collectionModel()->collection(index);
#else
            resource = static_cast<ResourceFilterModel*>(mListView->model())->resource(index);
#endif
        else
            mListView->clearSelection();
    }
    CalEvent::Type type = currentResourceType();
#ifdef USE_AKONADI
    bool haveCalendar = collection.isValid();
#else
    bool haveCalendar = resource;
#endif
    if (haveCalendar)
    {
#ifdef USE_AKONADI
        // Note: the CollectionControlModel functions call AkonadiModel::refresh(collection)
        active   = CollectionControlModel::isEnabled(collection, type);
        KACalendar::Compat compatibility;
        int rw = CollectionControlModel::isWritableEnabled(collection, type, compatibility);
        writable = (rw > 0);
        if (!rw
        &&  (compatibility & ~KACalendar::Converted)
        &&  !(compatibility & ~(KACalendar::Convertible | KACalendar::Converted)))
            updatable = true; // the calendar format is convertible to the current KAlarm format
        if (!(AkonadiModel::instance()->types(collection) & type))
            type = CalEvent::EMPTY;
#else
        active   = resource->isEnabled();
        type     = resource->alarmType();
        writable = resource->writable();
#endif
    }
    mActionReload->setEnabled(active);
    mActionShowDetails->setEnabled(haveCalendar);
    mActionSetColour->setEnabled(haveCalendar);
    mActionClearColour->setEnabled(haveCalendar);
#ifdef USE_AKONADI
    mActionClearColour->setVisible(AkonadiModel::instance()->backgroundColor(collection).isValid());
#else
    mActionClearColour->setVisible(resource && resource->colour().isValid());
    mActionSave->setEnabled(active && writable);
#endif
    mActionEdit->setEnabled(haveCalendar);
#ifdef USE_AKONADI
    mActionUpdate->setEnabled(updatable);
#endif
    mActionRemove->setEnabled(haveCalendar);
    mActionImport->setEnabled(active && writable);
    mActionExport->setEnabled(active);
    QString text;
    switch (type)
    {
        case CalEvent::ACTIVE:   text = i18nc("@action", "Use as &Default for Active Alarms");  break;
        case CalEvent::ARCHIVED: text = i18nc("@action", "Use as &Default for Archived Alarms");  break;
        case CalEvent::TEMPLATE: text = i18nc("@action", "Use as &Default for Alarm Templates");  break;
        default:  break;
    }
    mActionSetDefault->setText(text);
#ifdef USE_AKONADI
    bool standard = CollectionControlModel::isStandard(collection, type);
#else
    bool standard = (resource  &&  resource == mCalendar->getStandardResource(static_cast<CalEvent::Type>(type))  &&  resource->standardResource());
#endif
    mActionSetDefault->setChecked(active && writable && standard);
    mActionSetDefault->setEnabled(active && writable);
    mContextMenu->popup(mListView->viewport()->mapToGlobal(viewportPos));
}

/******************************************************************************
* Called from the context menu to reload the selected resource.
*/
void ResourceSelector::reloadResource()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
        AkonadiModel::instance()->reloadCollection(collection);
#else
    AlarmResource* resource = currentResource();
    if (resource)
        AlarmCalendar::resources()->loadResource(resource, this);
#endif
}

/******************************************************************************
* Called from the context menu to save the selected resource.
*/
void ResourceSelector::saveResource()
{
#ifdef USE_AKONADI
    // Save resource is not applicable to Akonadi
#else
    AlarmResource* resource = currentResource();
    if (resource)
        resource->save();
#endif
}

/******************************************************************************
* Called when the length of time archived alarms are to be stored changes.
* If expired alarms are now to be stored, set any single archived alarm
* resource to be the default.
*/
void ResourceSelector::archiveDaysChanged(int days)
{
    if (days)
    {
#ifdef USE_AKONADI
        if (!CollectionControlModel::getStandard(CalEvent::ARCHIVED).isValid())
        {
            Collection::List cols = CollectionControlModel::enabledCollections(CalEvent::ARCHIVED, true);
            if (cols.count() == 1)
            {
                CollectionControlModel::setStandard(cols[0], CalEvent::ARCHIVED);
                theApp()->purgeNewArchivedDefault(cols[0]);
            }
        }
#else
        AlarmResources* resources = AlarmResources::instance();
        AlarmResource* std = resources->getStandardResource(CalEvent::ARCHIVED);
        if (std  &&  !std->standardResource())
            resources->setStandardResource(std);
#endif
    }
}

/******************************************************************************
* Called from the context menu to set the selected resource as the default
* for its alarm type. The resource is automatically made active.
*/
void ResourceSelector::setStandard()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
    {
        CalEvent::Type alarmType = currentResourceType();
        bool standard = mActionSetDefault->isChecked();
        if (standard)
            CollectionControlModel::setEnabled(collection, alarmType, true);
        CollectionControlModel::setStandard(collection, alarmType, standard);
        if (alarmType == CalEvent::ARCHIVED)
            theApp()->purgeNewArchivedDefault(collection);
    }
#else
    AlarmResource* resource = currentResource();
    if (resource)
    {
        if (mActionSetDefault->isChecked())
        {
            resource->setEnabled(true);
            mCalendar->setStandardResource(resource);
        }
        else
            resource->setStandardResource(false);
    }
#endif
}

#ifndef USE_AKONADI
/******************************************************************************
* Called when a calendar status has changed.
*/
void ResourceSelector::slotStatusChanged(AlarmResource* resource, AlarmResources::Change change)
{
    if (change == AlarmResources::WrongType  &&  resource->isWrongAlarmType())
    {
        QString text;
        switch (resource->alarmType())
        {
            case CalEvent::ACTIVE:
                text = i18nc("@info/plain", "It is not an active alarm calendar.");
                break;
            case CalEvent::ARCHIVED:
                text = i18nc("@info/plain", "It is not an archived alarm calendar.");
                break;
            case CalEvent::TEMPLATE:
                text = i18nc("@info/plain", "It is not an alarm template calendar.");
                break;
            default:
                return;
        }
        KAMessageBox::sorry(this, i18nc("@info", "<para>Calendar <resource>%1</resource> has been disabled:</para><para>%2</para>", resource->resourceName(), text));
    }
}
#endif

/******************************************************************************
* Called from the context menu to merge alarms from an external calendar into
* the selected resource (if any).
*/
void ResourceSelector::importCalendar()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    AlarmCalendar::importAlarms(this, (collection.isValid() ? &collection : 0));
#else
    AlarmCalendar::importAlarms(this, currentResource());
#endif
}

/******************************************************************************
* Called from the context menu to copy the selected resource's alarms to an
* external calendar.
*/
void ResourceSelector::exportCalendar()
{
#ifdef USE_AKONADI
    Collection calendar = currentResource();
    if (calendar.isValid())
#else
    AlarmResource* calendar = currentResource();
    if (calendar)
#endif
        AlarmCalendar::exportAlarms(AlarmCalendar::resources()->events(calendar), this);
}

/******************************************************************************
* Called from the context menu to set a colour for the selected resource.
*/
void ResourceSelector::setColour()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
    {
        QColor colour = AkonadiModel::instance()->backgroundColor(collection);
        if (!colour.isValid())
            colour = QApplication::palette().color(QPalette::Base);
        if (KColorDialog::getColor(colour, QColor(), this) == KColorDialog::Accepted)
            AkonadiModel::instance()->setBackgroundColor(collection, colour);
    }
#else
    AlarmResource* resource = currentResource();
    if (resource)
    {
        QColor colour = resource->colour();
        if (!colour.isValid())
            colour = QApplication::palette().color(QPalette::Base);
        if (KColorDialog::getColor(colour, QColor(), this) == KColorDialog::Accepted)
            resource->setColour(colour);
    }
#endif
}

/******************************************************************************
* Called from the context menu to clear the display colour for the selected
* resource.
*/
void ResourceSelector::clearColour()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
        AkonadiModel::instance()->setBackgroundColor(collection, QColor());
#else
    AlarmResource* resource = currentResource();
    if (resource)
        resource->setColour(QColor());
#endif
}

/******************************************************************************
* Called from the context menu to display information for the selected resource.
*/
void ResourceSelector::showInfo()
{
#ifdef USE_AKONADI
    Collection collection = currentResource();
    if (collection.isValid())
    {
        const QString name = collection.displayName();
        QString id = collection.resource();   // resource name
        CalEvent::Type alarmType = currentResourceType();
        QString calType = AgentManager::self()->instance(id).type().name();
        QString storage = AkonadiModel::instance()->storageType(collection);
        QString location = collection.remoteId();
        KUrl url(location);
        if (url.isLocalFile())
            location = url.path();
        CalEvent::Types altypes = AkonadiModel::instance()->types(collection);
        QStringList alarmTypes;
        if (altypes & CalEvent::ACTIVE)
            alarmTypes << i18nc("@info/plain", "Active alarms");
        if (altypes & CalEvent::ARCHIVED)
            alarmTypes << i18nc("@info/plain", "Archived alarms");
        if (altypes & CalEvent::TEMPLATE)
            alarmTypes << i18nc("@info/plain", "Alarm templates");
        QString alarmTypeString = alarmTypes.join(i18nc("@info/plain List separator", ", "));
        KACalendar::Compat compat;
        QString perms = AkonadiModel::readOnlyTooltip(collection);
        if (perms.isEmpty())
            perms = i18nc("@info/plain", "Read-write");
        QString enabled = CollectionControlModel::isEnabled(collection, alarmType)
                    ? i18nc("@info/plain", "Enabled")
                    : i18nc("@info/plain", "Disabled");
        QString std = CollectionControlModel::isStandard(collection, alarmType)
                    ? i18nc("@info/plain Parameter in 'Default calendar: Yes/No'", "Yes")
                    : i18nc("@info/plain Parameter in 'Default calendar: Yes/No'", "No");
        QString text = i18nc("@info",
                             "<title>%1</title>"
                             "<para>ID: %2<nl/>"
                             "Calendar type: %3<nl/>"
                             "Contents: %4<nl/>"
                             "%5: <filename>%6</filename><nl/>"
                             "Permissions: %7<nl/>"
                             "Status: %8<nl/>"
                             "Default calendar: %9</para>",
                             name, id, calType, alarmTypeString, storage, location, perms, enabled, std);
        // Display the collection information. Because the user requested
        // the information, don't raise a KNotify event.
        KAMessageBox::information(this, text, QString(), QString(), 0);
    }
#else
    AlarmResource* resource = currentResource();
    if (resource)
    {
        // Display the collection information. Because the user requested
        // the information, don't raise a KNotify event.
        KAMessageBox::information(this, resource->infoText(), QString(), QString(), 0);
    }
#endif
}

/******************************************************************************
* Return the currently selected resource in the list.
*/
#ifdef USE_AKONADI
Collection ResourceSelector::currentResource() const
{
    return mListView->collection(mListView->selectionModel()->currentIndex());
}
#else
AlarmResource* ResourceSelector::currentResource() const
{
    return mListView->resource(mListView->selectionModel()->currentIndex());
}
#endif

/******************************************************************************
* Return the currently selected resource type.
*/
CalEvent::Type ResourceSelector::currentResourceType() const
{
    switch (mAlarmType->currentIndex())
    {
        case 0:  return CalEvent::ACTIVE;
        case 1:  return CalEvent::ARCHIVED;
        case 2:  return CalEvent::TEMPLATE;
        default:  return CalEvent::EMPTY;
    }
}

void ResourceSelector::resizeEvent(QResizeEvent* re)
{
    emit resized(re->oldSize(), re->size());
}

// vim: et sw=4:
