incidencedialog.cpp 31.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
/*
  Copyright (c) 2010 Bertjan Broeksema <broeksema@kde.org>
  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
  Copyright (C) 2012  Allen Winter <winter@kde.org>

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
  License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to the
  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.
*/

#include "incidencedialog.h"
#include "incidenceeditor_debug.h"
#include "combinedincidenceeditor.h"
#include "editorconfig.h"
#include "incidencealarm.h"
#include "incidenceattachment.h"
#include "incidenceattendee.h"
#include "incidencecategories.h"
#include "incidencecompletionpriority.h"
#include "incidencedatetime.h"
#include "incidencedescription.h"
#include "incidencerecurrence.h"
#include "incidenceresource.h"
#include "incidencesecrecy.h"
#include "incidencewhatwhere.h"
#include "templatemanagementdialog.h"
#include "ui_dialogdesktop.h"

#include <incidenceeditorsettings.h>

#include <CalendarSupport/KCalPrefs>
#include <CalendarSupport/Utils>

#include <CollectionComboBox>
#include <Item>
#include <Akonadi/Calendar/ETMCalendar>

49 50
#include <KCalendarCore/ICalFormat>
#include <KCalendarCore/MemoryCalendar>
51 52 53 54 55 56 57 58 59
#include <KCalUtils/Stringify>

#include <KMessageBox>
#include <KSharedConfig>

#include <QCloseEvent>
#include <QDir>
#include <QIcon>
#include <QStandardPaths>
Laurent Montel's avatar
Laurent Montel committed
60
#include <QTimeZone>
61 62 63

using namespace IncidenceEditorNG;

Laurent Montel's avatar
Laurent Montel committed
64
namespace IncidenceEditorNG {
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
enum Tabs {
    GeneralTab = 0,
    AttendeesTab,
    ResourcesTab,
    AlarmsTab,
    RecurrenceTab,
    AttachmentsTab
};

class IncidenceDialogPrivate : public ItemEditorUi
{
    IncidenceDialog *q_ptr;
    Q_DECLARE_PUBLIC(IncidenceDialog)

public:
Laurent Montel's avatar
Laurent Montel committed
80 81 82 83 84 85 86 87 88 89 90
    Ui::EventOrTodoDesktop *mUi = nullptr;
    Akonadi::CollectionComboBox *mCalSelector = nullptr;
    bool mCloseOnSave = false;

    EditorItemManager *mItemManager = nullptr;
    CombinedIncidenceEditor *mEditor = nullptr;
    IncidenceDateTime *mIeDateTime = nullptr;
    IncidenceAttendee *mIeAttendee = nullptr;
    IncidenceRecurrence *mIeRecurrence = nullptr;
    IncidenceResource *mIeResource = nullptr;
    bool mInitiallyDirty = false;
91 92 93 94 95
    Akonadi::Item mItem;
    QString typeToString(const int type) const;

public:
    IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq);
Laurent Montel's avatar
Laurent Montel committed
96
    ~IncidenceDialogPrivate() override;
97 98 99 100 101 102 103 104 105 106 107 108 109 110

    /// General methods
    void handleAlarmCountChange(int newCount);
    void handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type);
    void loadTemplate(const QString &templateName);
    void manageTemplates();
    void saveTemplate(const QString &templateName);
    void storeTemplatesInConfig(const QStringList &newTemplates);
    void updateAttachmentCount(int newCount);
    void updateAttendeeCount(int newCount);
    void updateResourceCount(int newCount);
    void updateButtonStatus(bool isDirty);
    void showMessage(const QString &text, KMessageWidget::MessageType type);
    void slotInvalidCollection();
111
    void setCalendarCollection(const Akonadi::Collection &collection);
112 113

    /// ItemEditorUi methods
114
    bool containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const override;
115 116
    void handleItemSaveFinish(EditorItemManager::SaveAction);
    void handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage);
117 118 119 120 121 122 123 124
    bool hasSupportedPayload(const Akonadi::Item &item) const override;
    bool isDirty() const override;
    bool isValid() const override;
    void load(const Akonadi::Item &item) override;
    Akonadi::Item save(const Akonadi::Item &item) override;
    Akonadi::Collection selectedCollection() const override;

    void reject(RejectReason reason, const QString &errorMessage = QString()) override;
125 126 127
};
}

Laurent Montel's avatar
Laurent Montel committed
128
IncidenceDialogPrivate::IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq)
Laurent Montel's avatar
Laurent Montel committed
129 130 131 132 133 134 135
    : q_ptr(qq)
    , mUi(new Ui::EventOrTodoDesktop)
    , mCalSelector(new Akonadi::CollectionComboBox)
    , mCloseOnSave(false)
    , mItemManager(new EditorItemManager(this, changer))
    , mEditor(new CombinedIncidenceEditor(qq))
    , mInitiallyDirty(false)
136 137
{
    Q_Q(IncidenceDialog);
Volker Krause's avatar
Volker Krause committed
138
    mUi->setupUi(q);
139
    mUi->mMessageWidget->hide();
140 141
    QGridLayout *layout = new QGridLayout(mUi->mCalSelectorPlaceHolder);
    layout->setSpacing(0);
142
    layout->setContentsMargins(0, 0, 0, 0);
143 144
    layout->addWidget(mCalSelector);
    mCalSelector->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
145
    mUi->label->setBuddy(mCalSelector);
Laurent Montel's avatar
Laurent Montel committed
146 147
    q->connect(mCalSelector, &Akonadi::CollectionComboBox::currentChanged, q,
               &IncidenceDialog::handleSelectedCollectionChange);
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

    // Now instantiate the logic of the dialog. These editors update the ui, validate
    // fields and load/store incidences in the ui.
    IncidenceWhatWhere *ieGeneral = new IncidenceWhatWhere(mUi);
    mEditor->combine(ieGeneral);

    IncidenceCategories *ieCategories = new IncidenceCategories(mUi);
    mEditor->combine(ieCategories);

    mIeDateTime = new IncidenceDateTime(mUi);
    mEditor->combine(mIeDateTime);

    IncidenceCompletionPriority *ieCompletionPriority = new IncidenceCompletionPriority(mUi);
    mEditor->combine(ieCompletionPriority);

    IncidenceDescription *ieDescription = new IncidenceDescription(mUi);
    mEditor->combine(ieDescription);

    IncidenceAlarm *ieAlarm = new IncidenceAlarm(mIeDateTime, mUi);
    mEditor->combine(ieAlarm);

    IncidenceAttachment *ieAttachments = new IncidenceAttachment(mUi);
    mEditor->combine(ieAttachments);

    mIeRecurrence = new IncidenceRecurrence(mIeDateTime, mUi);
    mEditor->combine(mIeRecurrence);

    IncidenceSecrecy *ieSecrecy = new IncidenceSecrecy(mUi);
    mEditor->combine(ieSecrecy);

    mIeAttendee = new IncidenceAttendee(qq, mIeDateTime, mUi);
    mIeAttendee->setParent(qq);
    mEditor->combine(mIeAttendee);

    mIeResource = new IncidenceResource(mIeAttendee, mIeDateTime, mUi);
    mEditor->combine(mIeResource);

185 186
    // Set the default collection
    const qint64 colId = CalendarSupport::KCalPrefs::instance()->defaultCalendarId();
187
    const Akonadi::Collection col(colId);
188
    setCalendarCollection(col);
189

190 191 192 193
    q->connect(mEditor, SIGNAL(showMessage(QString,KMessageWidget::MessageType)),
               SLOT(showMessage(QString,KMessageWidget::MessageType)));
    q->connect(mEditor, SIGNAL(dirtyStatusChanged(bool)),
               SLOT(updateButtonStatus(bool)));
Laurent Montel's avatar
Laurent Montel committed
194 195
    q->connect(mItemManager,
               SIGNAL(itemSaveFinished(IncidenceEditorNG::EditorItemManager::SaveAction)),
196
               SLOT(handleItemSaveFinish(IncidenceEditorNG::EditorItemManager::SaveAction)));
Laurent Montel's avatar
Laurent Montel committed
197 198
    q->connect(mItemManager,
               SIGNAL(itemSaveFailed(IncidenceEditorNG::EditorItemManager::SaveAction,QString)),
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
               SLOT(handleItemSaveFail(IncidenceEditorNG::EditorItemManager::SaveAction,QString)));
    q->connect(ieAlarm, SIGNAL(alarmCountChanged(int)),
               SLOT(handleAlarmCountChange(int)));
    q->connect(mIeRecurrence, SIGNAL(recurrenceChanged(IncidenceEditorNG::RecurrenceType)),
               SLOT(handleRecurrenceChange(IncidenceEditorNG::RecurrenceType)));
    q->connect(ieAttachments, SIGNAL(attachmentCountChanged(int)),
               SLOT(updateAttachmentCount(int)));
    q->connect(mIeAttendee, SIGNAL(attendeeCountChanged(int)),
               SLOT(updateAttendeeCount(int)));
    q->connect(mIeResource, SIGNAL(resourceCountChanged(int)),
               SLOT(updateResourceCount(int)));
}

IncidenceDialogPrivate::~IncidenceDialogPrivate()
{
    delete mItemManager;
    delete mEditor;
    delete mUi;
}

void IncidenceDialogPrivate::slotInvalidCollection()
{
    showMessage(i18n("Select a valid collection first."), KMessageWidget::Warning);
}

224 225 226 227 228
void IncidenceDialogPrivate::setCalendarCollection(const Akonadi::Collection &collection)
{
    if (collection.isValid()) {
        mCalSelector->setDefaultCollection(collection);
    } else {
229
        mCalSelector->setCurrentIndex(0);
230 231 232
    }
}

233 234 235 236 237 238 239 240 241 242 243
void IncidenceDialogPrivate::showMessage(const QString &text, KMessageWidget::MessageType type)
{
    mUi->mMessageWidget->setText(text);
    mUi->mMessageWidget->setMessageType(type);
    mUi->mMessageWidget->show();
}

void IncidenceDialogPrivate::handleAlarmCountChange(int newCount)
{
    QString tabText;
    if (newCount > 0) {
Laurent Montel's avatar
Laurent Montel committed
244 245 246
        tabText
            = i18nc("@title:tab Tab to configure the reminders of an event or todo",
                    "Reminder (%1)", newCount);
247
    } else {
Laurent Montel's avatar
Laurent Montel committed
248 249 250
        tabText
            = i18nc("@title:tab Tab to configure the reminders of an event or todo",
                    "Reminder");
251 252 253 254 255 256 257
    }

    mUi->mTabWidget->setTabText(AlarmsTab, tabText);
}

void IncidenceDialogPrivate::handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type)
{
Laurent Montel's avatar
Laurent Montel committed
258 259 260
    QString tabText
        = i18nc("@title:tab Tab to configure the recurrence of an event or todo",
                "Rec&urrence");
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281

    // Keep this numbers in sync with the items in mUi->mRecurrenceTypeCombo. I
    // tried adding an enum to IncidenceRecurrence but for whatever reason I could
    // Qt not play nice with namespaced enums in signal/slot connections.
    // Anyways, I don't expect these values to change.
    switch (type) {
    case RecurrenceTypeNone:
        break;
    case RecurrenceTypeDaily:
        tabText += i18nc("@title:tab Daily recurring event, capital first letter only", " (D)");
        break;
    case RecurrenceTypeWeekly:
        tabText += i18nc("@title:tab Weekly recurring event, capital first letter only", " (W)");
        break;
    case RecurrenceTypeMonthly:
        tabText += i18nc("@title:tab Monthly recurring event, capital first letter only", " (M)");
        break;
    case RecurrenceTypeYearly:
        tabText += i18nc("@title:tab Yearly recurring event, capital first letter only", " (Y)");
        break;
    case RecurrenceTypeException:
Laurent Montel's avatar
Laurent Montel committed
282 283
        tabText += i18nc("@title:tab Exception to a recurring event, capital first letter only",
                         " (E)");
284 285 286 287 288 289 290 291 292 293 294 295
        break;
    default:
        Q_ASSERT_X(false, "handleRecurrenceChange", "Fix your program");
    }

    mUi->mTabWidget->setTabText(RecurrenceTab, tabText);
}

QString IncidenceDialogPrivate::typeToString(const int type) const
{
    // Do not translate.
    switch (type) {
296
    case KCalendarCore::Incidence::TypeEvent:
297
        return QStringLiteral("Event");
298
    case KCalendarCore::Incidence::TypeTodo:
299
        return QStringLiteral("Todo");
300
    case KCalendarCore::Incidence::TypeJournal:
301 302 303 304 305 306 307 308 309 310
        return QStringLiteral("Journal");
    default:
        return QStringLiteral("Unknown");
    }
}

void IncidenceDialogPrivate::loadTemplate(const QString &templateName)
{
    Q_Q(IncidenceDialog);

311
    KCalendarCore::MemoryCalendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
312 313

    const QString fileName = QStandardPaths::locate(
Laurent Montel's avatar
Laurent Montel committed
314 315 316 317
        QStandardPaths::GenericDataLocation,
        QStringLiteral("/korganizer/templates/")
        +typeToString(mEditor->type()) + QLatin1Char('/')
        +templateName);
318 319 320 321

    if (fileName.isEmpty()) {
        KMessageBox::error(
            q,
322
            i18nc("@info", "Unable to find template '%1'.", templateName));
323 324 325
        return;
    }

326
    KCalendarCore::ICalFormat format;
327 328 329 330 331 332 333
    if (!format.load(cal, fileName)) {
        KMessageBox::error(
            q,
            i18nc("@info", "Error loading template file '%1'.", fileName));
        return;
    }

334
    KCalendarCore::Incidence::List incidences = cal->incidences();
335 336 337 338 339 340 341 342
    if (incidences.isEmpty()) {
        KMessageBox::error(
            q,
            i18nc("@info", "Template does not contain a valid incidence."));
        return;
    }

    mIeDateTime->setActiveDate(QDate());
343 344
    KCalendarCore::Incidence::Ptr newInc = KCalendarCore::Incidence::Ptr(incidences.first()->clone());
    newInc->setUid(KCalendarCore::CalFormat::createUniqueId());
345 346 347 348 349 350 351 352 353 354 355

    // We add a custom property so that some fields aren't loaded, dates for example
    newInc->setCustomProperty(QByteArray("kdepim"), "isTemplate", QStringLiteral("true"));
    mEditor->load(newInc);
    newInc->removeCustomProperty(QByteArray(), "isTemplate");
}

void IncidenceDialogPrivate::manageTemplates()
{
    Q_Q(IncidenceDialog);

Laurent Montel's avatar
Laurent Montel committed
356 357
    QStringList &templates
        = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

    QPointer<IncidenceEditorNG::TemplateManagementDialog> dialog(
        new IncidenceEditorNG::TemplateManagementDialog(
            q, templates, KCalUtils::Stringify::incidenceType(mEditor->type())));

    q->connect(dialog, SIGNAL(loadTemplate(QString)),
               SLOT(loadTemplate(QString)));
    q->connect(dialog, SIGNAL(templatesChanged(QStringList)),
               SLOT(storeTemplatesInConfig(QStringList)));
    q->connect(dialog, SIGNAL(saveTemplate(QString)),
               SLOT(saveTemplate(QString)));
    dialog->exec();
    delete dialog;
}

void IncidenceDialogPrivate::saveTemplate(const QString &templateName)
{
Laurent Montel's avatar
Laurent Montel committed
375
    Q_ASSERT(!templateName.isEmpty());
376

377
    KCalendarCore::MemoryCalendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
378 379

    switch (mEditor->type()) {
380
    case KCalendarCore::Incidence::TypeEvent:
Laurent Montel's avatar
Laurent Montel committed
381
    {
382
        KCalendarCore::Event::Ptr event(new KCalendarCore::Event());
383
        mEditor->save(event);
384
        cal->addEvent(KCalendarCore::Event::Ptr(event->clone()));
385 386
        break;
    }
387
    case KCalendarCore::Incidence::TypeTodo:
Laurent Montel's avatar
Laurent Montel committed
388
    {
389
        KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
390
        mEditor->save(todo);
391
        cal->addTodo(KCalendarCore::Todo::Ptr(todo->clone()));
392 393
        break;
    }
394
    case KCalendarCore::Incidence::TypeJournal:
Laurent Montel's avatar
Laurent Montel committed
395
    {
396
        KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
397
        mEditor->save(journal);
398
        cal->addJournal(KCalendarCore::Journal::Ptr(journal->clone()));
399 400 401 402 403 404
        break;
    }
    default:
        Q_ASSERT_X(false, "saveTemplate", "Fix your program");
    }

Laurent Montel's avatar
Laurent Montel committed
405 406 407
    QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
                       +QStringLiteral("/korganizer/templates/") + typeToString(mEditor->type())
                       + QLatin1Char('/');
Laurent Montel's avatar
Laurent Montel committed
408
    QDir().mkpath(fileName);
409 410
    fileName += templateName;

411
    KCalendarCore::ICalFormat format;
412 413 414 415 416 417 418
    format.save(cal, fileName);
}

void IncidenceDialogPrivate::storeTemplatesInConfig(const QStringList &templateNames)
{
    // I find this somewhat broken. templates() returns a reference, maybe it should
    // be changed by adding a setTemplates method.
Laurent Montel's avatar
Laurent Montel committed
419 420 421 422 423 424
    const QStringList origTemplates
        = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
    const QString defaultPath
        = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
          +QStringLiteral("korganizer/templates/")
          +typeToString(mEditor->type()) + QLatin1Char('/');
Laurent Montel's avatar
Laurent Montel committed
425
    QDir().mkpath(defaultPath);
Laurent Montel's avatar
Laurent Montel committed
426
    for (const QString &tmpl : origTemplates) {
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
        if (!templateNames.contains(tmpl)) {
            const QString fileName = defaultPath + tmpl;
            QFile file(fileName);
            if (file.exists()) {
                file.remove();
            }
        }
    }

    IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()) = templateNames;
    IncidenceEditorNG::EditorConfig::instance()->config()->save();
}

void IncidenceDialogPrivate::updateAttachmentCount(int newCount)
{
    if (newCount > 0) {
        mUi->mTabWidget->setTabText(
            AttachmentsTab,
            i18nc("@title:tab Tab to modify attachments of an event or todo",
                  "Attac&hments (%1)", newCount));
    } else {
        mUi->mTabWidget->setTabText(
            AttachmentsTab,
            i18nc("@title:tab Tab to modify attachments of an event or todo",
                  "Attac&hments"));
    }
}

void IncidenceDialogPrivate::updateAttendeeCount(int newCount)
{
    if (newCount > 0) {
        mUi->mTabWidget->setTabText(
            AttendeesTab,
            i18nc("@title:tab Tab to modify attendees of an event or todo",
                  "&Attendees (%1)", newCount));
    } else {
        mUi->mTabWidget->setTabText(
            AttendeesTab,
            i18nc("@title:tab Tab to modify attendees of an event or todo",
                  "&Attendees"));
    }
}

void IncidenceDialogPrivate::updateResourceCount(int newCount)
{
    if (newCount > 0) {
        mUi->mTabWidget->setTabText(
            ResourcesTab,
            i18nc("@title:tab Tab to modify attendees of an event or todo",
                  "&Resources (%1)", newCount));
    } else {
        mUi->mTabWidget->setTabText(
            ResourcesTab,
            i18nc("@title:tab Tab to modify attendees of an event or todo",
                  "&Resources"));
    }
}

void IncidenceDialogPrivate::updateButtonStatus(bool isDirty)
{
Volker Krause's avatar
Volker Krause committed
487
    mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty || mInitiallyDirty);
488 489 490 491 492 493 494 495
}

bool IncidenceDialogPrivate::containsPayloadIdentifiers(
    const QSet<QByteArray> &partIdentifiers) const
{
    return partIdentifiers.contains(QByteArray("PLD:RFC822"));
}

Laurent Montel's avatar
Laurent Montel committed
496
void IncidenceDialogPrivate::handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage)
497 498 499 500 501 502 503 504
{
    Q_Q(IncidenceDialog);

    bool retry = false;

    if (!errorMessage.isEmpty()) {
        const QString message = i18nc("@info",
                                      "Unable to store the incidence in the calendar. Try again?\n\n "
Laurent Montel's avatar
Laurent Montel committed
505 506
                                      "Reason: %1",
                                      errorMessage);
507 508 509 510 511 512 513
        retry = (KMessageBox::warningYesNo(q, message) == KMessageBox::Yes);
    }

    if (retry) {
        mItemManager->save();
    } else {
        updateButtonStatus(isDirty());
Volker Krause's avatar
Volker Krause committed
514 515
        mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
        mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
516 517 518 519 520 521 522 523 524 525 526 527 528
    }
}

void IncidenceDialogPrivate::handleItemSaveFinish(EditorItemManager::SaveAction saveAction)
{
    Q_Q(IncidenceDialog);

    if (mCloseOnSave) {
        q->accept();
    } else {
        const Akonadi::Item item = mItemManager->item();
        Q_ASSERT(item.isValid());
        Q_ASSERT(item.hasPayload());
529
        Q_ASSERT(item.hasPayload<KCalendarCore::Incidence::Ptr>());
530
        // Now the item is successfully saved, reload it in the editor in order to
531
        // reset the dirty status of the editor.
532
        mEditor->load(item.payload<KCalendarCore::Incidence::Ptr>());
533 534 535 536
        mEditor->load(item);

        // Set the buttons to a reasonable state as well (ok and apply should be
        // disabled at this point).
Volker Krause's avatar
Volker Krause committed
537 538 539
        mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
        mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
        mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty());
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
    }

    if (saveAction == EditorItemManager::Create) {
        Q_EMIT q->incidenceCreated(mItemManager->item());
    }
}

bool IncidenceDialogPrivate::hasSupportedPayload(const Akonadi::Item &item) const
{
    return CalendarSupport::incidence(item);
}

bool IncidenceDialogPrivate::isDirty() const
{
    if (mItem.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
555 556
        return mEditor->isDirty()
               || mCalSelector->currentCollection().id() != mItem.storageCollectionId();
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
    } else {
        return mEditor->isDirty();
    }
}

bool IncidenceDialogPrivate::isValid() const
{
    Q_Q(const IncidenceDialog);
    if (mEditor->isValid()) {
        // Check if there's a selected collection.
        if (mCalSelector->currentCollection().isValid()) {
            return true;
        } else {
            qCWarning(INCIDENCEEDITOR_LOG) << "Select a collection first";
            Q_EMIT q->invalidCollection();
        }
    }

    return false;
}

void IncidenceDialogPrivate::load(const Akonadi::Item &item)
{
    Q_Q(IncidenceDialog);

    Q_ASSERT(hasSupportedPayload(item));

    if (CalendarSupport::hasJournal(item)) {
        //mUi->mTabWidget->removeTab(5);
        mUi->mTabWidget->removeTab(AttachmentsTab);
        mUi->mTabWidget->removeTab(RecurrenceTab);
        mUi->mTabWidget->removeTab(AlarmsTab);
        mUi->mTabWidget->removeTab(AttendeesTab);
        mUi->mTabWidget->removeTab(ResourcesTab);
    }

    mEditor->load(CalendarSupport::incidence(item));
    mEditor->load(item);

596
    const KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
597
    const QStringList allEmails = IncidenceEditorNG::EditorConfig::instance()->allEmails();
598
    const KCalendarCore::Attendee me = incidence->attendeeByMails(allEmails);
599

Laurent Montel's avatar
Laurent Montel committed
600
    if (incidence->attendeeCount() > 1     // >1 because you won't drink alone
601
        && !me.isNull() && (me.status() == KCalendarCore::Attendee::NeedsAction
Laurent Montel's avatar
Laurent Montel committed
602 603
                            || me.status() == KCalendarCore::Attendee::Tentative
                            || me.status() == KCalendarCore::Attendee::InProcess)) {
604 605 606 607 608 609
        // Show the invitation bar: "You are invited [accept] [decline]"
        mUi->mInvitationBar->show();
    } else {
        mUi->mInvitationBar->hide();
    }

Laurent Montel's avatar
Laurent Montel committed
610 611
    qCDebug(INCIDENCEEDITOR_LOG) << "Loading item " << item.id() << "; parent "
                                 << item.parentCollection().id()
612 613 614 615 616 617
                                 << "; storage " << item.storageCollectionId();

    if (item.storageCollectionId() > -1) {
        mCalSelector->setDefaultCollection(Akonadi::Collection(item.storageCollectionId()));
    }

618
    if (!mCalSelector->mimeTypeFilter().contains(QLatin1String("text/calendar"))
Laurent Montel's avatar
Laurent Montel committed
619 620 621
        || !mCalSelector->mimeTypeFilter().contains(incidence->mimeType())) {
        mCalSelector->setMimeTypeFilter(QStringList() << incidence->mimeType()
                                                      << QStringLiteral("text/calendar"));
622 623
    }

624
    if (mEditor->type() == KCalendarCore::Incidence::TypeTodo) {
625
        q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")));
626
    } else if (mEditor->type() == KCalendarCore::Incidence::TypeEvent) {
627
        q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-day")));
628
    } else if (mEditor->type() == KCalendarCore::Incidence::TypeJournal) {
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
        q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-pim-journal")));
    }

    // Initialize tab's titles
    updateAttachmentCount(incidence->attachments().size());
    updateResourceCount(mIeResource->resourceCount());
    updateAttendeeCount(mIeAttendee->attendeeCount());
    handleRecurrenceChange(mIeRecurrence->currentRecurrenceType());
    handleAlarmCountChange(incidence->alarms().count());

    mItem = item;

    q->show();
}

Akonadi::Item IncidenceDialogPrivate::save(const Akonadi::Item &item)
{
646
    Q_ASSERT(mEditor->incidence<KCalendarCore::Incidence>());
647

648 649
    KCalendarCore::Incidence::Ptr incidenceInEditor = mEditor->incidence<KCalendarCore::Incidence>();
    KCalendarCore::Incidence::Ptr newIncidence(incidenceInEditor->clone());
650 651 652 653 654 655 656 657 658 659 660 661 662 663

    Akonadi::Item result = item;
    result.setMimeType(newIncidence->mimeType());

    // There's no editor that has the relatedTo property. We must set it here, by hand.
    // Otherwise it gets lost.
    // FIXME: Why don't we clone() incidenceInEditor then pass the clone to save(),
    // I wonder if we're not leaking other properties.
    newIncidence->setRelatedTo(incidenceInEditor->relatedTo());

    mEditor->save(newIncidence);
    mEditor->save(result);

    // Make sure that we don't loose uid for existing incidence
664
    newIncidence->setUid(mEditor->incidence<KCalendarCore::Incidence>()->uid());
665 666 667 668 669 670

    // Mark the incidence as changed
    if (mItem.isValid()) {
        newIncidence->setRevision(newIncidence->revision() + 1);
    }

671
    result.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence);
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
    return result;
}

Akonadi::Collection IncidenceDialogPrivate::selectedCollection() const
{
    return mCalSelector->currentCollection();
}

void IncidenceDialogPrivate::reject(RejectReason reason, const QString &errorMessage)
{
    Q_UNUSED(reason);

    Q_Q(IncidenceDialog);
    qCCritical(INCIDENCEEDITOR_LOG) << "Rejecting:" << errorMessage;
    q->deleteLater();
}

/// IncidenceDialog

Laurent Montel's avatar
Laurent Montel committed
691
IncidenceDialog::IncidenceDialog(Akonadi::IncidenceChanger *changer, QWidget *parent, Qt::WindowFlags flags)
Laurent Montel's avatar
Laurent Montel committed
692 693
    : QDialog(parent, flags)
    , d_ptr(new IncidenceDialogPrivate(changer, this))
694 695 696 697 698 699 700
{
    Q_D(IncidenceDialog);
    setAttribute(Qt::WA_DeleteOnClose);

    d->mUi->mTabWidget->setCurrentIndex(0);
    d->mUi->mSummaryEdit->setFocus();

Laurent Montel's avatar
Laurent Montel committed
701 702 703 704 705 706
    d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip",
                                                                         "Save current changes"));
    d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@action:button",
                                                                      "Save changes and close dialog"));
    d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setToolTip(i18nc("@action:button",
                                                                          "Discard changes and close dialog"));
Volker Krause's avatar
Volker Krause committed
707 708 709 710 711 712 713
    d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);

    auto defaultButton = d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults);
    defaultButton->setText(i18nc("@action:button", "&Templates..."));
    defaultButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template")));
    defaultButton->setToolTip(i18nc("@info:tooltip", "Manage templates for this item"));
    defaultButton->setWhatsThis(
Laurent Montel's avatar
Laurent Montel committed
714 715 716 717 718 719
        i18nc("@info:whatsthis",
              "Push this button to show a dialog that helps "
              "you manage a set of templates. Templates "
              "can make creating new items easier and faster "
              "by putting your favorite default values into "
              "the editor automatically."));
720

Laurent Montel's avatar
Laurent Montel committed
721 722
    connect(d->mUi->buttonBox, &QDialogButtonBox::clicked, this,
            &IncidenceDialog::slotButtonClicked);
Volker Krause's avatar
Volker Krause committed
723

724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
    setModal(false);

    connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked,
            d->mIeAttendee, &IncidenceAttendee::acceptForMe);
    connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked,
            d->mUi->mInvitationBar, &QWidget::hide);
    connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked,
            d->mIeAttendee, &IncidenceAttendee::declineForMe);
    connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked,
            d->mUi->mInvitationBar, &QWidget::hide);
    connect(this, SIGNAL(invalidCollection()),
            this, SLOT(slotInvalidCollection()));
    readConfig();
}

IncidenceDialog::~IncidenceDialog()
{
    writeConfig();
    delete d_ptr;
}

void IncidenceDialog::writeConfig()
{
    KConfigGroup group(KSharedConfig::openConfig(), "IncidenceDialog");
    group.writeEntry("Size", size());
}

void IncidenceDialog::readConfig()
{
    KConfigGroup group(KSharedConfig::openConfig(), "IncidenceDialog");
    const QSize size = group.readEntry("Size", QSize());
    if (size.isValid()) {
        resize(size);
    } else {
        resize(QSize(500, 500).expandedTo(minimumSizeHint()));
    }
}

void IncidenceDialog::load(const Akonadi::Item &item, const QDate &activeDate)
{
    Q_D(IncidenceDialog);
    d->mIeDateTime->setActiveDate(activeDate);
    if (item.isValid()) {   // We're editing
        d->mItemManager->load(item);
    } else { // We're creating
        Q_ASSERT(d->hasSupportedPayload(item));
        d->load(item);
        show();
    }
}

void IncidenceDialog::selectCollection(const Akonadi::Collection &collection)
{
    Q_D(IncidenceDialog);
778
    d->setCalendarCollection(collection);
779 780 781 782 783 784 785 786 787 788 789 790 791 792
}

void IncidenceDialog::setIsCounterProposal(bool isCounterProposal)
{
    Q_D(IncidenceDialog);
    d->mItemManager->setIsCounterProposal(isCounterProposal);
}

QObject *IncidenceDialog::typeAheadReceiver() const
{
    Q_D(const IncidenceDialog);
    return d->mUi->mSummaryEdit;
}

Volker Krause's avatar
Volker Krause committed
793
void IncidenceDialog::slotButtonClicked(QAbstractButton *button)
794 795 796
{
    Q_D(IncidenceDialog);

Volker Krause's avatar
Volker Krause committed
797
    if (d->mUi->buttonBox->button(QDialogButtonBox::Ok) == button) {
798
        if (d->isDirty() || d->mInitiallyDirty) {
Volker Krause's avatar
Volker Krause committed
799 800 801
            d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
            d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
            d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
802 803 804 805 806 807
            d->mCloseOnSave = true;
            d->mInitiallyDirty = false;
            d->mItemManager->save();
        } else {
            close();
        }
Volker Krause's avatar
Volker Krause committed
808 809 810 811
    } else if (d->mUi->buttonBox->button(QDialogButtonBox::Apply) == button) {
        d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
        d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
        d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
812 813 814 815

        d->mCloseOnSave = false;
        d->mInitiallyDirty = false;
        d->mItemManager->save();
Volker Krause's avatar
Volker Krause committed
816
    } else if (d->mUi->buttonBox->button(QDialogButtonBox::Cancel) == button) {
Laurent Montel's avatar
Laurent Montel committed
817 818 819 820 821
        if (d->isDirty()
            && KMessageBox::questionYesNo(
                this,
                i18nc("@info", "Do you really want to cancel?"),
                i18nc("@title:window", "KOrganizer Confirmation")) == KMessageBox::Yes) {
Volker Krause's avatar
Volker Krause committed
822
            QDialog::reject(); // Discard current changes
823
        } else if (!d->isDirty()) {
Volker Krause's avatar
Volker Krause committed
824
            QDialog::reject(); // No pending changes, just close the dialog.
825
        } // else { // the user wasn't finished editing after all }
Laurent Montel's avatar
Laurent Montel committed
826
    } else if (d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
827
        d->manageTemplates();
Volker Krause's avatar
Volker Krause committed
828
    } else {
829 830 831 832
        Q_ASSERT(false);   // Shouldn't happen
    }
}

833 834 835 836 837 838 839 840 841 842 843 844 845 846
void IncidenceDialog::reject()
{
    Q_D(IncidenceDialog);
    if (d->isDirty()
        && KMessageBox::questionYesNo(
            this,
            i18nc("@info", "Do you really want to cancel?"),
            i18nc("@title:window", "KOrganizer Confirmation")) == KMessageBox::Yes) {
        QDialog::reject(); // Discard current changes
    } else if (!d->isDirty()) {
        QDialog::reject(); // No pending changes, just close the dialog.
    }
}

847 848 849
void IncidenceDialog::closeEvent(QCloseEvent *event)
{
    Q_D(IncidenceDialog);
Laurent Montel's avatar
Laurent Montel committed
850 851 852 853 854
    if (d->isDirty()
        && KMessageBox::questionYesNo(
            this,
            i18nc("@info", "Do you really want to cancel?"),
            i18nc("@title:window", "KOrganizer Confirmation")) == KMessageBox::Yes) {
Volker Krause's avatar
Volker Krause committed
855 856
        QDialog::reject(); // Discard current changes
        QDialog::closeEvent(event);
857
    } else if (!d->isDirty()) {
Volker Krause's avatar
Volker Krause committed
858 859
        QDialog::reject(); // No pending changes, just close the dialog.
        QDialog::closeEvent(event);
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
    } else {
        event->ignore();
    }
}

void IncidenceDialog::setInitiallyDirty(bool initiallyDirty)
{
    Q_D(IncidenceDialog);
    d->mInitiallyDirty = initiallyDirty;
}

Akonadi::Item IncidenceDialog::item() const
{
    Q_D(const IncidenceDialog);
    return d->mItemManager->item();
}

void IncidenceDialog::handleSelectedCollectionChange(const Akonadi::Collection &collection)
{
    Q_D(IncidenceDialog);
    if (d->mItem.parentCollection().isValid()) {
Laurent Montel's avatar
Laurent Montel committed
881 882
        d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(
            collection.id() != d->mItem.parentCollection().id());
883 884 885 886
    }
}

#include "moc_incidencedialog.cpp"