resourcedatamodelbase.cpp 30.4 KB
Newer Older
1
/*
2
 *  resourcedatamodelbase.cpp  -  base for models containing calendars and events
3
 *  Program:  kalarm
4
 *  Copyright © 2007-2020 David Jarvie <djarvie@kde.org>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 *  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.
 */

21
#include "resourcedatamodelbase.h"
22

23
#include "resources.h"
24

25
#include "preferences.h"
26
#include "lib/desktop.h"
David Jarvie's avatar
David Jarvie committed
27
#include "lib/messagebox.h"
28
#include "kalarm_debug.h"
29

30
31
#include <KAlarmCal/AlarmText>
#include <KAlarmCal/KAEvent>
32
33
34

#include <KLocalizedString>

35
#include <QApplication>
36
37
#include <QIcon>

38
39
40
41
42
43
namespace
{
QString alarmTimeText(const DateTime& dateTime, char leadingZero = '\0');
QString timeToAlarmText(const DateTime& dateTime);
}

44
/*=============================================================================
45
= Class: ResourceDataModelBase
46
47
=============================================================================*/

48
49
ResourceDataModelBase* ResourceDataModelBase::mInstance = nullptr;

50
51
52
53
54
55
QPixmap* ResourceDataModelBase::mTextIcon    = nullptr;
QPixmap* ResourceDataModelBase::mFileIcon    = nullptr;
QPixmap* ResourceDataModelBase::mCommandIcon = nullptr;
QPixmap* ResourceDataModelBase::mEmailIcon   = nullptr;
QPixmap* ResourceDataModelBase::mAudioIcon   = nullptr;
QSize    ResourceDataModelBase::mIconSize;
56
57
58
59

/******************************************************************************
* Constructor.
*/
60
ResourceDataModelBase::ResourceDataModelBase()
61
62
63
64
65
66
67
68
69
70
71
72
{
    if (!mTextIcon)
    {
        mTextIcon    = new QPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(16, 16));
        mFileIcon    = new QPixmap(QIcon::fromTheme(QStringLiteral("document-open")).pixmap(16, 16));
        mCommandIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("system-run")).pixmap(16, 16));
        mEmailIcon   = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-unread")).pixmap(16, 16));
        mAudioIcon   = new QPixmap(QIcon::fromTheme(QStringLiteral("audio-x-generic")).pixmap(16, 16));
        mIconSize = mTextIcon->size().expandedTo(mFileIcon->size()).expandedTo(mCommandIcon->size()).expandedTo(mEmailIcon->size()).expandedTo(mAudioIcon->size());
    }
}

73
ResourceDataModelBase::~ResourceDataModelBase()
74
75
76
{
}

David Jarvie's avatar
David Jarvie committed
77
78
79
/******************************************************************************
* Create a bulleted list of alarm types for insertion into <para>...</para>.
*/
80
QString ResourceDataModelBase::typeListForDisplay(CalEvent::Types alarmTypes)
David Jarvie's avatar
David Jarvie committed
81
82
83
{
    QString list;
    if (alarmTypes & CalEvent::ACTIVE)
David Jarvie's avatar
David Jarvie committed
84
        list += QLatin1String("<item>") + i18nc("@item:intext", "Active Alarms") + QLatin1String("</item>");
David Jarvie's avatar
David Jarvie committed
85
    if (alarmTypes & CalEvent::ARCHIVED)
David Jarvie's avatar
David Jarvie committed
86
        list += QLatin1String("<item>") + i18nc("@item:intext", "Archived Alarms") + QLatin1String("</item>");
David Jarvie's avatar
David Jarvie committed
87
    if (alarmTypes & CalEvent::TEMPLATE)
David Jarvie's avatar
David Jarvie committed
88
        list += QLatin1String("<item>") + i18nc("@item:intext", "Alarm Templates") + QLatin1String("</item>");
David Jarvie's avatar
David Jarvie committed
89
90
91
92
93
    if (!list.isEmpty())
        list = QLatin1String("<list>") + list + QLatin1String("</list>");
    return list;
}

94
95
96
97
98
/******************************************************************************
* Return the read-only status tooltip for a collection, determined by the
* read-write permissions and the KAlarm calendar format compatibility.
* A null string is returned if the collection is read-write and compatible.
*/
99
QString ResourceDataModelBase::readOnlyTooltip(const Resource& resource)
100
101
102
103
{
    switch (resource.compatibility())
    {
        case KACalendar::Current:
David Jarvie's avatar
David Jarvie committed
104
            return resource.readOnly() ? i18nc("@item:intext Calendar status", "Read-only") : QString();
105
106
        case KACalendar::Converted:
        case KACalendar::Convertible:
David Jarvie's avatar
David Jarvie committed
107
            return i18nc("@item:intext Calendar status", "Read-only (old format)");
108
109
        case KACalendar::Incompatible:
        default:
David Jarvie's avatar
David Jarvie committed
110
            return i18nc("@item:intext Calendar status", "Read-only (other format)");
111
112
113
    }
}

114
115
116
/******************************************************************************
* Return data for a column heading.
*/
117
QVariant ResourceDataModelBase::headerData(int section, Qt::Orientation orientation, int role, bool eventHeaders, bool& handled)
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
{
    if (orientation == Qt::Horizontal)
    {
        handled = true;
        if (eventHeaders)
        {
            // Event column headers
            if (section < 0  ||  section >= ColumnCount)
                return QVariant();
            if (role == Qt::DisplayRole  ||  role == ColumnTitleRole)
            {
                switch (section)
                {
                    case TimeColumn:
                        return i18nc("@title:column", "Time");
                    case TimeToColumn:
                        return i18nc("@title:column", "Time To");
                    case RepeatColumn:
                        return i18nc("@title:column", "Repeat");
                    case ColourColumn:
                        return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Color");
                    case TypeColumn:
                        return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Type");
                    case TextColumn:
                        return i18nc("@title:column", "Message, File or Command");
                    case TemplateNameColumn:
                        return i18nc("@title:column Template name", "Name");
                }
            }
            else if (role == Qt::WhatsThisRole)
                return whatsThisText(section);
        }
        else
        {
            // Calendar column headers
            if (section != 0)
                return QVariant();
            if (role == Qt::DisplayRole)
                return i18nc("@title:column", "Calendars");
        }
    }

    handled = false;
    return QVariant();
}

164
165
166
/******************************************************************************
* Return whether resourceData() or eventData() handle a role.
*/
167
bool ResourceDataModelBase::roleHandled(int role) const
168
{
169
170
    switch (role)
    {
171
172
173
174
175
176
177
178
179
        case Qt::WhatsThisRole:
        case Qt::ForegroundRole:
        case Qt::BackgroundRole:
        case Qt::DisplayRole:
        case Qt::TextAlignmentRole:
        case Qt::DecorationRole:
        case Qt::SizeHintRole:
        case Qt::AccessibleTextRole:
        case Qt::ToolTipRole:
David Jarvie's avatar
David Jarvie committed
180
181
        case ItemTypeRole:
        case ResourceIdRole:
182
183
184
185
186
        case BaseColourRole:
        case TimeDisplayRole:
        case SortRole:
        case StatusRole:
        case ValueRole:
David Jarvie's avatar
David Jarvie committed
187
188
        case EventIdRole:
        case ParentResourceIdRole:
189
        case EnabledRole:
190
191
192
        case AlarmActionsRole:
        case AlarmSubActionRole:
        case CommandErrorRole:
193
            return true;
194
        default:
195
            return false;
196
    }
197
198
199
200
201
}

/******************************************************************************
* Return the data for a given role, for a specified resource.
*/
202
QVariant ResourceDataModelBase::resourceData(int& role, const Resource& resource, bool& handled) const
203
{
David Jarvie's avatar
David Jarvie committed
204
    if (roleHandled(role))   // Ensure that roleHandled() is coded correctly
205
    {
206
207
208
209
210
211
212
213
214
        handled = true;
        switch (role)
        {
            case Qt::DisplayRole:
                return resource.displayName();
            case BaseColourRole:
                role = Qt::BackgroundRole;   // use base model background colour
                break;
            case Qt::BackgroundRole:
215
            {
216
217
218
                const QColor colour = resource.backgroundColour();
                if (colour.isValid())
                    return colour;
David Jarvie's avatar
David Jarvie committed
219
                break;    // use base model background colour
220
221
222
223
224
            }
            case Qt::ForegroundRole:
                return resource.foregroundColour();
            case Qt::ToolTipRole:
                return tooltip(resource, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE);
David Jarvie's avatar
David Jarvie committed
225
226
227
228
            case ItemTypeRole:
                return static_cast<int>(Type::Resource);
            case ResourceIdRole:
                return resource.id();
229
230
231
232
233
234
235
236
237
238
239
240
            default:
                break;
        }
    }

    handled = false;
    return QVariant();
}

/******************************************************************************
* Return the data for a given role, for a specified event.
*/
241
242
QVariant ResourceDataModelBase::eventData(int role, int column, const KAEvent& event,
                                          const Resource& resource, bool& handled) const
243
{
David Jarvie's avatar
David Jarvie committed
244
    if (roleHandled(role))   // Ensure that roleHandled() is coded correctly
245
246
247
248
    {
        handled = true;
        bool calendarColour = false;

David Jarvie's avatar
David Jarvie committed
249
250
251
252
253
254
255
256
257
        switch (role)
        {
            case Qt::WhatsThisRole:
                return whatsThisText(column);
            case ItemTypeRole:
                return static_cast<int>(Type::Event);
            default:
                break;
        }
258
259
260
261
        if (!event.isValid())
            return QVariant();
        switch (role)
        {
David Jarvie's avatar
David Jarvie committed
262
263
            case EventIdRole:
                return event.id();
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
            case StatusRole:
                return event.category();
            case AlarmActionsRole:
                return event.actionTypes();
            case AlarmSubActionRole:
                return event.actionSubType();
            case CommandErrorRole:
                return event.commandError();
            default:
                break;
        }
        switch (column)
        {
            case TimeColumn:
                switch (role)
279
                {
280
281
282
283
284
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DisplayRole:
                        if (event.expired())
285
286
                            return alarmTimeText(event.startDateTime(), '0');
                        return alarmTimeText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER), '0');
287
288
                    case TimeDisplayRole:
                        if (event.expired())
289
290
                            return alarmTimeText(event.startDateTime(), '~');
                        return alarmTimeText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER), '~');
291
292
293
294
295
296
297
298
299
300
301
302
303
304
                    case Qt::TextAlignmentRole:
                        return Qt::AlignRight;
                    case SortRole:
                    {
                        DateTime due;
                        if (event.expired())
                            due = event.startDateTime();
                        else
                            due = event.nextTrigger(KAEvent::DISPLAY_TRIGGER);
                        return due.isValid() ? due.effectiveKDateTime().toUtc().qDateTime()
                                             : QDateTime(QDate(9999,12,31), QTime(0,0,0));
                    }
                    default:
                        break;
305
                }
306
307
308
                break;
            case TimeToColumn:
                switch (role)
309
                {
310
311
312
313
314
315
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DisplayRole:
                        if (event.expired())
                            return QString();
316
                        return timeToAlarmText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER));
317
318
319
320
321
322
323
324
325
326
327
328
                    case Qt::TextAlignmentRole:
                        return Qt::AlignRight;
                    case SortRole:
                    {
                        if (event.expired())
                            return -1;
                        const DateTime due = event.nextTrigger(KAEvent::DISPLAY_TRIGGER);
                        const KADateTime now = KADateTime::currentUtcDateTime();
                        if (due.isDateOnly())
                            return now.date().daysTo(due.date()) * 1440;
                        return (now.secsTo(due.effectiveKDateTime()) + 59) / 60;
                    }
329
                }
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
                break;
            case RepeatColumn:
                switch (role)
                {
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DisplayRole:
                        return repeatText(event);
                    case Qt::TextAlignmentRole:
                        return Qt::AlignHCenter;
                    case SortRole:
                        return repeatOrder(event);
                }
                break;
            case ColourColumn:
                switch (role)
347
                {
348
                    case Qt::BackgroundRole:
349
                    {
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
                        const KAEvent::Actions type = event.actionTypes();
                        if (type & KAEvent::ACT_DISPLAY)
                            return event.bgColour();
                        if (type == KAEvent::ACT_COMMAND)
                        {
                            if (event.commandError() != KAEvent::CMD_NO_ERROR)
                                return QColor(Qt::red);
                        }
                        break;
                    }
                    case Qt::ForegroundRole:
                        if (event.commandError() != KAEvent::CMD_NO_ERROR)
                        {
                            if (event.actionTypes() == KAEvent::ACT_COMMAND)
                                return QColor(Qt::white);
                            QColor colour = Qt::red;
                            int r, g, b;
                            event.bgColour().getRgb(&r, &g, &b);
                            if (r > 128  &&  g <= 128  &&  b <= 128)
                                colour = QColor(Qt::white);
                            return colour;
                        }
                        break;
                    case Qt::DisplayRole:
374
                        if (event.commandError() != KAEvent::CMD_NO_ERROR)
375
376
377
378
379
380
381
                            return QLatin1String("!");
                        break;
                    case SortRole:
                    {
                        const unsigned i = (event.actionTypes() == KAEvent::ACT_DISPLAY)
                                           ? event.bgColour().rgb() : 0;
                        return QStringLiteral("%1").arg(i, 6, 10, QLatin1Char('0'));
382
                    }
383
384
                    default:
                        break;
385
                }
386
387
388
389
390
391
392
393
                break;
            case TypeColumn:
                switch (role)
                {
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DecorationRole:
394
                    {
395
396
397
                        QVariant v;
                        v.setValue(*eventIcon(event));
                        return v;
398
                    }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
                    case Qt::TextAlignmentRole:
                        return Qt::AlignHCenter;
                    case Qt::SizeHintRole:
                        return mIconSize;
                    case Qt::AccessibleTextRole:
//TODO: Implement accessibility
                        return QString();
                    case ValueRole:
                        return static_cast<int>(event.actionSubType());
                    case SortRole:
                        return QStringLiteral("%1").arg(event.actionSubType(), 2, 10, QLatin1Char('0'));
                }
                break;
            case TextColumn:
                switch (role)
414
                {
415
416
417
418
419
420
421
422
423
424
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DisplayRole:
                    case SortRole:
                        return AlarmText::summary(event, 1);
                    case Qt::ToolTipRole:
                        return AlarmText::summary(event, 10);
                    default:
                        break;
425
                }
426
427
428
                break;
            case TemplateNameColumn:
                switch (role)
429
                {
430
431
432
433
434
435
436
                    case Qt::BackgroundRole:
                        calendarColour = true;
                        break;
                    case Qt::DisplayRole:
                        return event.templateName();
                    case SortRole:
                        return event.templateName().toUpper();
437
                }
438
439
440
441
                break;
            default:
                break;
        }
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
        if (calendarColour)
        {
            const QColor colour = resource.backgroundColour();
            if (colour.isValid())
                return colour;
        }

        switch (role)
        {
            case Qt::ForegroundRole:
                if (!event.enabled())
                   return Preferences::disabledColour();
                if (event.expired())
                   return Preferences::archivedColour();
                break;   // use the default for normal active alarms
            case Qt::ToolTipRole:
                // Show the last command execution error message
                switch (event.commandError())
                {
                    case KAEvent::CMD_ERROR:
                        return i18nc("@info:tooltip", "Command execution failed");
                    case KAEvent::CMD_ERROR_PRE:
                        return i18nc("@info:tooltip", "Pre-alarm action execution failed");
                    case KAEvent::CMD_ERROR_POST:
                        return i18nc("@info:tooltip", "Post-alarm action execution failed");
                    case KAEvent::CMD_ERROR_PRE_POST:
                        return i18nc("@info:tooltip", "Pre- and post-alarm action execution failed");
                    default:
                    case KAEvent::CMD_NO_ERROR:
472
473
474
                        // Return empty string to cancel any previous tooltip -
                        // returning QVariant() leaves tooltip unchanged.
                        return QString();
475
476
477
478
479
480
481
                }
                break;
            case EnabledRole:
                return event.enabled();
            default:
                break;
        }
482
483
    }

484
    handled = false;
485
486
487
488
    return QVariant();
}

/******************************************************************************
489
* Return a resource's tooltip text. The resource's enabled status is
490
491
* evaluated for specified alarm types.
*/
492
QString ResourceDataModelBase::tooltip(const Resource& resource, CalEvent::Types types) const
493
{
494
495
496
497
498
499
    const QString name     = QLatin1Char('@') + resource.displayName();   // insert markers for stripping out name
    const QString type     = QLatin1Char('@') + resource.storageTypeString(false);   // file/directory/URL etc.
    const QString locn     = resource.displayLocation();
    const bool    inactive = !(resource.enabledTypes() & types);
    const QString readonly = readOnlyTooltip(resource);
    const bool    writable = readonly.isEmpty();
David Jarvie's avatar
David Jarvie committed
500
    const QString disabled = i18nc("@item:intext Calendar status", "Disabled");
501
502
    if (inactive  ||  !writable)
        return xi18nc("@info:tooltip",
David Jarvie's avatar
David Jarvie committed
503
504
505
506
                      "%1"
                      "<nl/>%2: <filename>%3</filename>"
                      "<nl/>%4",
                      name, type, locn, (inactive ? disabled : readonly));
507
    return xi18nc("@info:tooltip",
David Jarvie's avatar
David Jarvie committed
508
509
510
                  "%1"
                  "<nl/>%2: <filename>%3</filename>",
                  name, type, locn);
511
512
513
514
515
}

/******************************************************************************
* Return the repetition text.
*/
516
QString ResourceDataModelBase::repeatText(const KAEvent& event)
517
518
519
520
521
522
523
524
{
    const QString repText = event.recurrenceText(true);
    return repText.isEmpty() ? event.repetitionText(true) : repText;
}

/******************************************************************************
* Return a string for sorting the repetition column.
*/
525
QString ResourceDataModelBase::repeatOrder(const KAEvent& event)
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
{
    int repOrder = 0;
    int repInterval = 0;
    if (event.repeatAtLogin())
        repOrder = 1;
    else
    {
        repInterval = event.recurInterval();
        switch (event.recurType())
        {
            case KARecurrence::MINUTELY:
                repOrder = 2;
                break;
            case KARecurrence::DAILY:
                repOrder = 3;
                break;
            case KARecurrence::WEEKLY:
                repOrder = 4;
                break;
            case KARecurrence::MONTHLY_DAY:
            case KARecurrence::MONTHLY_POS:
                repOrder = 5;
                break;
            case KARecurrence::ANNUAL_DATE:
            case KARecurrence::ANNUAL_POS:
                repOrder = 6;
                break;
            case KARecurrence::NO_RECUR:
            default:
                break;
        }
    }
    return QStringLiteral("%1%2").arg(static_cast<char>('0' + repOrder)).arg(repInterval, 8, 10, QLatin1Char('0'));
}

/******************************************************************************
* Returns the QWhatsThis text for a specified column.
*/
564
QString ResourceDataModelBase::whatsThisText(int column)
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
{
    switch (column)
    {
        case TimeColumn:
            return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm");
        case TimeToColumn:
            return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm");
        case RepeatColumn:
            return i18nc("@info:whatsthis", "How often the alarm recurs");
        case ColourColumn:
            return i18nc("@info:whatsthis", "Background color of alarm message");
        case TypeColumn:
            return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)");
        case TextColumn:
            return i18nc("@info:whatsthis", "Alarm message text, URL of text file to display, command to execute, or email subject line");
        case TemplateNameColumn:
            return i18nc("@info:whatsthis", "Name of the alarm template");
        default:
            return QString();
    }
}

/******************************************************************************
* Return the icon associated with an event's action.
*/
590
QPixmap* ResourceDataModelBase::eventIcon(const KAEvent& event)
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
{
    switch (event.actionTypes())
    {
        case KAEvent::ACT_EMAIL:
            return mEmailIcon;
        case KAEvent::ACT_AUDIO:
            return mAudioIcon;
        case KAEvent::ACT_COMMAND:
            return mCommandIcon;
        case KAEvent::ACT_DISPLAY:
            if (event.actionSubType() == KAEvent::FILE)
                return mFileIcon;
            Q_FALLTHROUGH();    // fall through to ACT_DISPLAY_COMMAND
        case KAEvent::ACT_DISPLAY_COMMAND:
        default:
            return mTextIcon;
    }
}

610
611
612
/******************************************************************************
* Display a message to the user.
*/
613
void ResourceDataModelBase::handleResourceMessage(ResourceType::MessageType type, const QString& message, const QString& details)
614
{
615
    if (type == ResourceType::MessageType::Error)
616
617
    {
        qCDebug(KALARM_LOG) << "Resource Error!" << message << details;
618
        KAMessageBox::detailedError(Desktop::mainWindow(), message, details);
619
    }
620
    else if (type == ResourceType::MessageType::Info)
621
622
623
624
    {
        qCDebug(KALARM_LOG) << "Resource user message:" << message << details;
        // KMessageBox::informationList looks bad, so use our own formatting.
        const QString msg = details.isEmpty() ? message : message + QStringLiteral("\n\n") + details;
625
        KAMessageBox::information(Desktop::mainWindow(), msg);
626
    }
627
628
}

629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
bool ResourceDataModelBase::isMigrationComplete() const
{
    return mMigrationStatus == 1;
}

bool ResourceDataModelBase::isMigrating() const
{
    return mMigrationStatus == 0;
}

void ResourceDataModelBase::setMigrationInitiated(bool started)
{
    mMigrationStatus = (started ? 0 : -1);
}

void ResourceDataModelBase::setMigrationComplete()
{
    mMigrationStatus = 1;
647
648
649
650
651
652
653
654
655
    if (mCreationStatus)
        Resources::notifyResourcesCreated();
}

void ResourceDataModelBase::setCalendarsCreated()
{
    mCreationStatus = true;
    if (mMigrationStatus == 1)
        Resources::notifyResourcesCreated();
656
657
}

658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
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
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
namespace
{

/******************************************************************************
* Return the alarm time text in the form "date time".
* Parameters:
*   dateTime    = the date/time to format.
*   leadingZero = the character to represent a leading zero, or '\0' for no leading zeroes.
*/
QString alarmTimeText(const DateTime& dateTime, char leadingZero)
{
    // Whether the date and time contain leading zeroes.
    static bool    leadingZeroesChecked = false;
    static QString dateFormat;      // date format for current locale
    static QString timeFormat;      // time format for current locale
    static QString timeFullFormat;  // time format with leading zero, if different from 'timeFormat'
    static int     hourOffset = 0;  // offset within time string to the hour

    if (!dateTime.isValid())
        return i18nc("@info Alarm never occurs", "Never");
    if (!leadingZeroesChecked  &&  QApplication::isLeftToRight())    // don't try to align right-to-left languages
    {
        // Check whether the day number and/or hour have no leading zeroes, if
        // they are at the start of the date/time. If no leading zeroes, they
        // will need to be padded when displayed, so that displayed dates/times
        // can be aligned with each other.
        // Note that if leading zeroes are not included in other components, no
        // alignment will be attempted.
        QLocale locale;
        {
            // Check the date format. 'dd' provides leading zeroes; single 'd'
            // provides no leading zeroes.
            dateFormat = locale.dateFormat(QLocale::ShortFormat);
        }
        {
            // Check the time format.
            // Remove all but hours, minutes and AM/PM, since alarms are on minute
            // boundaries. Preceding separators are also removed.
            timeFormat = locale.timeFormat(QLocale::ShortFormat);
            for (int del = 0, predel = 0, c = 0;  c < timeFormat.size();  ++c)
            {
                char ch = timeFormat.at(c).toLatin1();
                switch (ch)
                {
                    case 'H':
                    case 'h':
                    case 'm':
                    case 'a':
                    case 'A':
                        if (predel == 1)
                        {
                            timeFormat.remove(del, c - del);
                            c = del;
                        }
                        del = c + 1;   // start deleting from the next character
                        if ((ch == 'A'  &&  del < timeFormat.size()  &&  timeFormat.at(del).toLatin1() == 'P')
                        ||  (ch == 'a'  &&  del < timeFormat.size()  &&  timeFormat.at(del).toLatin1() == 'p'))
                            ++c, ++del;
                        predel = -1;
                        break;

                    case 's':
                    case 'z':
                    case 't':
                        timeFormat.remove(del, c + 1 - del);
                        c = del - 1;
                        if (!predel)
                            predel = 1;
                        break;

                    default:
                        break;
                }
            }

            // 'HH' and 'hh' provide leading zeroes; single 'H' or 'h' provide no
            // leading zeroes.
            int i = timeFormat.indexOf(QRegExp(QLatin1String("[hH]")));
            int first = timeFormat.indexOf(QRegExp(QLatin1String("[hHmaA]")));
            if (i >= 0  &&  i == first  &&  (i == timeFormat.size() - 1  ||  timeFormat.at(i) != timeFormat.at(i + 1)))
            {
                timeFullFormat = timeFormat;
                timeFullFormat.insert(i, timeFormat.at(i));
                // Find index to hour in formatted times
                const QTime t(1,30,30);
                const QString nozero = t.toString(timeFormat);
                const QString zero   = t.toString(timeFullFormat);
                for (int i = 0; i < nozero.size(); ++i)
                    if (nozero[i] != zero[i])
                    {
                        hourOffset = i;
                        break;
                    }
            }
        }
    }
    leadingZeroesChecked = true;

    const KADateTime kdt = dateTime.effectiveKDateTime().toTimeSpec(Preferences::timeSpec());
    QString dateTimeText = kdt.date().toString(dateFormat);

    if (!dateTime.isDateOnly()  ||  kdt.utcOffset() != dateTime.utcOffset())
    {
        // Display the time of day if it's a date/time value, or if it's
        // a date-only value but it's in a different time zone
        dateTimeText += QLatin1Char(' ');
        bool useFullFormat = leadingZero && !timeFullFormat.isEmpty();
        QString timeText = kdt.time().toString(useFullFormat ? timeFullFormat : timeFormat);
        if (useFullFormat  &&  leadingZero != '0'  &&  timeText.at(hourOffset) == QLatin1Char('0'))
            timeText[hourOffset] = leadingZero;
        dateTimeText += timeText;
    }
    return dateTimeText + QLatin1Char(' ');
}

/******************************************************************************
* Return the time-to-alarm text.
*/
QString timeToAlarmText(const DateTime& dateTime)
{
    if (!dateTime.isValid())
        return i18nc("@info Alarm never occurs", "Never");
    KADateTime now = KADateTime::currentUtcDateTime();
    if (dateTime.isDateOnly())
    {
        int days = now.date().daysTo(dateTime.date());
        // xgettext: no-c-format
        return i18nc("@info n days", "%1d", days);
    }
    int mins = (now.secsTo(dateTime.effectiveKDateTime()) + 59) / 60;
    if (mins < 0)
        return QString();
    char minutes[3] = "00";
    minutes[0] = (mins%60) / 10 + '0';
    minutes[1] = (mins%60) % 10 + '0';
    if (mins < 24*60)
        return i18nc("@info hours:minutes", "%1:%2", mins/60, QLatin1String(minutes));
    // If we render a day count, then we zero-pad the hours, to make the days line up and be more scanable.
    int hrs = mins / 60;
    char hours[3] = "00";
    hours[0] = (hrs%24) / 10 + '0';
    hours[1] = (hrs%24) % 10 + '0';
    int days = hrs / 24;
    return i18nc("@info days hours:minutes", "%1d %2:%3", days, QLatin1String(hours), QLatin1String(minutes));
}

}

806
// vim: et sw=4: