File indexing completed on 2024-06-02 05:19:22
0001 /* 0002 * resourcedatamodelbase.cpp - base for models containing calendars and events 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2007-2023 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "resourcedatamodelbase.h" 0010 0011 #include "resources.h" 0012 #include "lib/desktop.h" 0013 #include "lib/messagebox.h" 0014 #include "kalarmcalendar/alarmtext.h" 0015 #include "kalarmcalendar/kaevent.h" 0016 #include "kalarm_debug.h" 0017 0018 #include <KLocalizedString> 0019 0020 #include <QApplication> 0021 #include <QIcon> 0022 #include <QRegularExpression> 0023 0024 0025 /*============================================================================= 0026 = Class: ResourceDataModelBase 0027 =============================================================================*/ 0028 0029 ResourceDataModelBase* ResourceDataModelBase::mInstance = nullptr; 0030 0031 QPixmap* ResourceDataModelBase::mTextIcon = nullptr; 0032 QPixmap* ResourceDataModelBase::mFileIcon = nullptr; 0033 QPixmap* ResourceDataModelBase::mCommandIcon = nullptr; 0034 QPixmap* ResourceDataModelBase::mEmailIcon = nullptr; 0035 QPixmap* ResourceDataModelBase::mAudioIcon = nullptr; 0036 QSize ResourceDataModelBase::mIconSize; 0037 0038 /****************************************************************************** 0039 * Constructor. 0040 */ 0041 ResourceDataModelBase::ResourceDataModelBase() 0042 { 0043 if (!mTextIcon) 0044 { 0045 mTextIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(16, 16)); 0046 mFileIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("document-open")).pixmap(16, 16)); 0047 mCommandIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("system-run")).pixmap(16, 16)); 0048 mEmailIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-unread")).pixmap(16, 16)); 0049 mAudioIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("audio-x-generic")).pixmap(16, 16)); 0050 mIconSize = mTextIcon->size().expandedTo(mFileIcon->size()).expandedTo(mCommandIcon->size()).expandedTo(mEmailIcon->size()).expandedTo(mAudioIcon->size()); 0051 } 0052 } 0053 0054 ResourceDataModelBase::~ResourceDataModelBase() 0055 { 0056 } 0057 0058 /****************************************************************************** 0059 * Create a bulleted list of alarm types for insertion into <para>...</para>. 0060 */ 0061 QString ResourceDataModelBase::typeListForDisplay(CalEvent::Types alarmTypes) 0062 { 0063 QString list; 0064 if (alarmTypes & CalEvent::ACTIVE) 0065 list += QLatin1String("<item>") + i18nc("@item:intext", "Active Alarms") + QLatin1String("</item>"); 0066 if (alarmTypes & CalEvent::ARCHIVED) 0067 list += QLatin1String("<item>") + i18nc("@item:intext", "Archived Alarms") + QLatin1String("</item>"); 0068 if (alarmTypes & CalEvent::TEMPLATE) 0069 list += QLatin1String("<item>") + i18nc("@item:intext", "Alarm Templates") + QLatin1String("</item>"); 0070 if (!list.isEmpty()) 0071 list = QLatin1String("<list>") + list + QLatin1String("</list>"); 0072 return list; 0073 } 0074 0075 /****************************************************************************** 0076 * Return the read-only status tooltip for a collection, determined by the 0077 * read-write permissions and the KAlarm calendar format compatibility. 0078 * A null string is returned if the collection is read-write and compatible. 0079 */ 0080 QString ResourceDataModelBase::readOnlyTooltip(const Resource& resource) 0081 { 0082 switch (resource.compatibility()) 0083 { 0084 case KACalendar::Current: 0085 return resource.readOnly() ? i18nc("@item:intext Calendar status", "Read-only") : QString(); 0086 case KACalendar::Converted: 0087 case KACalendar::Convertible: 0088 return i18nc("@item:intext Calendar status", "Read-only (old format)"); 0089 case KACalendar::Incompatible: 0090 default: 0091 return i18nc("@item:intext Calendar status", "Read-only (other format)"); 0092 } 0093 } 0094 0095 /****************************************************************************** 0096 * Return data for a column heading. 0097 */ 0098 QVariant ResourceDataModelBase::headerData(int section, Qt::Orientation orientation, int role, bool eventHeaders, bool& handled) 0099 { 0100 if (orientation == Qt::Horizontal) 0101 { 0102 handled = true; 0103 if (eventHeaders) 0104 { 0105 // Event column headers 0106 if (section < 0 || section >= ColumnCount) 0107 return {}; 0108 if (role == Qt::DisplayRole || role == ColumnTitleRole) 0109 { 0110 switch (section) 0111 { 0112 case TimeColumn: 0113 return i18nc("@title:column", "Time"); 0114 case TimeToColumn: 0115 return i18nc("@title:column", "Time To"); 0116 case RepeatColumn: 0117 return i18nc("@title:column", "Repeat"); 0118 case ColourColumn: 0119 return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Color"); 0120 case TypeColumn: 0121 return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Type"); 0122 case NameColumn: 0123 return i18nc("@title:column", "Name"); 0124 case TextColumn: 0125 return i18nc("@title:column", "Message, File or Command"); 0126 case TemplateNameColumn: 0127 return i18nc("@title:column Template name", "Name"); 0128 } 0129 } 0130 else if (role == Qt::WhatsThisRole) 0131 return whatsThisText(section); 0132 } 0133 else 0134 { 0135 // Calendar column headers 0136 if (section != 0) 0137 return {}; 0138 if (role == Qt::DisplayRole) 0139 return i18nc("@title:column", "Calendars"); 0140 } 0141 } 0142 0143 handled = false; 0144 return {}; 0145 } 0146 0147 /****************************************************************************** 0148 * Return whether resourceData() or eventData() handle a role. 0149 */ 0150 bool ResourceDataModelBase::roleHandled(int role) const 0151 { 0152 switch (role) 0153 { 0154 case Qt::WhatsThisRole: 0155 case Qt::ForegroundRole: 0156 case Qt::BackgroundRole: 0157 case Qt::DisplayRole: 0158 case Qt::TextAlignmentRole: 0159 case Qt::DecorationRole: 0160 case Qt::SizeHintRole: 0161 case Qt::AccessibleTextRole: 0162 case Qt::ToolTipRole: 0163 case ItemTypeRole: 0164 case ResourceIdRole: 0165 case BaseColourRole: 0166 case TimeDisplayRole: 0167 case SortRole: 0168 case StatusRole: 0169 case ValueRole: 0170 case EventIdRole: 0171 case ParentResourceIdRole: 0172 case EnabledRole: 0173 case AlarmActionsRole: 0174 case AlarmSubActionRole: 0175 case CommandErrorRole: 0176 return true; 0177 default: 0178 return false; 0179 } 0180 } 0181 0182 /****************************************************************************** 0183 * Return the data for a given role, for a specified resource. 0184 */ 0185 QVariant ResourceDataModelBase::resourceData(int& role, const Resource& resource, bool& handled) const 0186 { 0187 if (roleHandled(role)) // Ensure that roleHandled() is coded correctly 0188 { 0189 handled = true; 0190 switch (role) 0191 { 0192 case Qt::DisplayRole: 0193 return resource.displayName(); 0194 case BaseColourRole: 0195 role = Qt::BackgroundRole; // use base model background colour 0196 break; 0197 case Qt::BackgroundRole: 0198 { 0199 const QColor colour = resource.backgroundColour(); 0200 if (colour.isValid()) 0201 return colour; 0202 break; // use base model background colour 0203 } 0204 case Qt::ForegroundRole: 0205 return resource.foregroundColour(); 0206 case Qt::ToolTipRole: 0207 return tooltip(resource, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE); 0208 case ItemTypeRole: 0209 return static_cast<int>(Type::Resource); 0210 case ResourceIdRole: 0211 return resource.id(); 0212 default: 0213 break; 0214 } 0215 } 0216 0217 handled = false; 0218 return {}; 0219 } 0220 0221 /****************************************************************************** 0222 * Return the data for a given role, for a specified event. 0223 */ 0224 QVariant ResourceDataModelBase::eventData(int role, int column, const KAEvent& event, 0225 const Resource& resource, bool& handled) const 0226 { 0227 if (roleHandled(role)) // Ensure that roleHandled() is coded correctly 0228 { 0229 handled = true; 0230 bool calendarColour = false; 0231 0232 switch (role) 0233 { 0234 case Qt::WhatsThisRole: 0235 return whatsThisText(column); 0236 case ItemTypeRole: 0237 return static_cast<int>(Type::Event); 0238 default: 0239 break; 0240 } 0241 if (!event.isValid()) 0242 return {}; 0243 switch (role) 0244 { 0245 case EventIdRole: 0246 return event.id(); 0247 case StatusRole: 0248 return event.category(); 0249 case AlarmActionsRole: 0250 return static_cast<int>(event.actionTypes()); 0251 case AlarmSubActionRole: 0252 return static_cast<int>(event.actionSubType()); 0253 case CommandErrorRole: 0254 return static_cast<int>(event.commandError()); 0255 default: 0256 break; 0257 } 0258 switch (column) 0259 { 0260 case TimeColumn: 0261 switch (role) 0262 { 0263 case Qt::BackgroundRole: 0264 calendarColour = true; 0265 break; 0266 case Qt::DisplayRole: 0267 if (event.expired()) 0268 return alarmTimeText(event.startDateTime(), '0'); 0269 return alarmTimeText(event.nextTrigger(KAEvent::Trigger::Display), '0'); 0270 case TimeDisplayRole: 0271 if (event.expired()) 0272 return alarmTimeText(event.startDateTime(), '~'); 0273 return alarmTimeText(event.nextTrigger(KAEvent::Trigger::Display), '~'); 0274 case Qt::TextAlignmentRole: 0275 return Qt::AlignLeft; 0276 case SortRole: 0277 { 0278 DateTime due; 0279 if (event.expired()) 0280 due = event.startDateTime(); 0281 else 0282 due = event.nextTrigger(KAEvent::Trigger::Display); 0283 return due.isValid() ? due.effectiveKDateTime().toUtc().qDateTime() 0284 : QDateTime(QDate(9999,12,31), QTime(0,0,0)); 0285 } 0286 default: 0287 break; 0288 } 0289 break; 0290 case TimeToColumn: 0291 switch (role) 0292 { 0293 case Qt::BackgroundRole: 0294 calendarColour = true; 0295 break; 0296 case Qt::DisplayRole: 0297 if (event.expired()) 0298 return QString(); 0299 return timeToAlarmText(event.nextTrigger(KAEvent::Trigger::Display)); 0300 case Qt::TextAlignmentRole: 0301 return Qt::AlignRight; 0302 case SortRole: 0303 { 0304 if (event.expired()) 0305 return -1; 0306 const DateTime due = event.nextTrigger(KAEvent::Trigger::Display); 0307 const KADateTime now = KADateTime::currentUtcDateTime(); 0308 if (due.isDateOnly()) 0309 return now.date().daysTo(due.date()) * 1440; 0310 return (now.secsTo(due.effectiveKDateTime()) + 59) / 60; 0311 } 0312 } 0313 break; 0314 case RepeatColumn: 0315 switch (role) 0316 { 0317 case Qt::BackgroundRole: 0318 calendarColour = true; 0319 break; 0320 case Qt::DisplayRole: 0321 return repeatText(event); 0322 case Qt::TextAlignmentRole: 0323 return Qt::AlignHCenter; 0324 case SortRole: 0325 return repeatOrder(event); 0326 } 0327 break; 0328 case ColourColumn: 0329 switch (role) 0330 { 0331 case Qt::BackgroundRole: 0332 { 0333 const KAEvent::Action type = event.actionTypes(); 0334 if (type & KAEvent::Action::Display) 0335 return event.bgColour(); 0336 if (type == KAEvent::Action::Command) 0337 { 0338 if (event.commandError() != KAEvent::CmdErr::None) 0339 return QColor(Qt::red); 0340 } 0341 break; 0342 } 0343 case Qt::ForegroundRole: 0344 if (event.commandError() != KAEvent::CmdErr::None) 0345 { 0346 if (event.actionTypes() == KAEvent::Action::Command) 0347 return QColor(Qt::white); 0348 QColor colour = Qt::red; 0349 int r, g, b; 0350 event.bgColour().getRgb(&r, &g, &b); 0351 if (r > 128 && g <= 128 && b <= 128) 0352 colour = QColor(Qt::white); 0353 return colour; 0354 } 0355 break; 0356 case Qt::DisplayRole: 0357 if (event.commandError() != KAEvent::CmdErr::None) 0358 return QLatin1String("!"); 0359 break; 0360 case Qt::TextAlignmentRole: 0361 return Qt::AlignCenter; 0362 case SortRole: 0363 { 0364 const unsigned i = (event.actionTypes() == KAEvent::Action::Display) 0365 ? event.bgColour().rgb() : 0; 0366 return QStringLiteral("%1").arg(i, 6, 10, QLatin1Char('0')); 0367 } 0368 default: 0369 break; 0370 } 0371 break; 0372 case TypeColumn: 0373 switch (role) 0374 { 0375 case Qt::BackgroundRole: 0376 calendarColour = true; 0377 break; 0378 case Qt::DecorationRole: 0379 { 0380 QVariant v; 0381 v.setValue(*eventIcon(event)); 0382 return v; 0383 } 0384 case Qt::TextAlignmentRole: 0385 return Qt::AlignHCenter; 0386 case Qt::SizeHintRole: 0387 return mIconSize; 0388 case Qt::AccessibleTextRole: 0389 //TODO: Implement accessibility 0390 return QString(); 0391 case ValueRole: 0392 return static_cast<int>(event.actionSubType()); 0393 case SortRole: 0394 return QStringLiteral("%1").arg(static_cast<int>(event.actionSubType()), 2, 10, QLatin1Char('0')); 0395 } 0396 break; 0397 case NameColumn: 0398 case TemplateNameColumn: 0399 switch (role) 0400 { 0401 case Qt::BackgroundRole: 0402 calendarColour = true; 0403 break; 0404 case Qt::DisplayRole: 0405 case Qt::ToolTipRole: 0406 return event.name(); 0407 case SortRole: 0408 return event.name().toUpper(); 0409 } 0410 break; 0411 case TextColumn: 0412 switch (role) 0413 { 0414 case Qt::BackgroundRole: 0415 calendarColour = true; 0416 break; 0417 case Qt::DisplayRole: 0418 case SortRole: 0419 return AlarmText::summary(event, 1); 0420 case Qt::ToolTipRole: 0421 return AlarmText::summary(event, 10); 0422 default: 0423 break; 0424 } 0425 break; 0426 default: 0427 break; 0428 } 0429 0430 if (calendarColour) 0431 { 0432 const QColor colour = resource.backgroundColour(); 0433 if (colour.isValid()) 0434 return colour; 0435 } 0436 0437 switch (role) 0438 { 0439 case Qt::ForegroundRole: 0440 if (!event.enabled()) 0441 return Preferences::disabledColour(); 0442 if (event.expired()) 0443 return Preferences::archivedColour(); 0444 break; // use the default for normal active alarms 0445 case Qt::ToolTipRole: 0446 // Show the last command execution error message 0447 switch (event.commandError()) 0448 { 0449 case KAEvent::CmdErr::Fail: 0450 return i18nc("@info:tooltip", "Command execution failed"); 0451 case KAEvent::CmdErr::Pre: 0452 return i18nc("@info:tooltip", "Pre-alarm action execution failed"); 0453 case KAEvent::CmdErr::Post: 0454 return i18nc("@info:tooltip", "Post-alarm action execution failed"); 0455 case KAEvent::CmdErr::PrePost: 0456 return i18nc("@info:tooltip", "Pre- and post-alarm action execution failed"); 0457 default: 0458 case KAEvent::CmdErr::None: 0459 // Return empty string to cancel any previous tooltip - 0460 // returning QVariant() leaves tooltip unchanged. 0461 return QString(); 0462 } 0463 break; 0464 case EnabledRole: 0465 return event.enabled(); 0466 default: 0467 break; 0468 } 0469 } 0470 0471 handled = false; 0472 return {}; 0473 } 0474 0475 /****************************************************************************** 0476 * Return a resource's tooltip text. The resource's enabled status is 0477 * evaluated for specified alarm types. 0478 */ 0479 QString ResourceDataModelBase::tooltip(const Resource& resource, CalEvent::Types types) const 0480 { 0481 const QString name = QLatin1Char('@') + resource.displayName(); // insert markers for stripping out name 0482 const QString type = QLatin1Char('@') + resource.storageTypeString(false); // file/directory/URL etc. 0483 const QString locn = resource.displayLocation(); 0484 const bool inactive = !(resource.enabledTypes() & types); 0485 const QString readonly = readOnlyTooltip(resource); 0486 const bool writable = readonly.isEmpty(); 0487 const QString disabled = i18nc("@item:intext Calendar status", "Disabled"); 0488 if (inactive || !writable) 0489 return xi18nc("@info:tooltip", 0490 "%1" 0491 "<nl/>%2: <filename>%3</filename>" 0492 "<nl/>%4", 0493 name, type, locn, (inactive ? disabled : readonly)); 0494 return xi18nc("@info:tooltip", 0495 "%1" 0496 "<nl/>%2: <filename>%3</filename>", 0497 name, type, locn); 0498 } 0499 0500 /****************************************************************************** 0501 * Return the repetition text. 0502 */ 0503 QString ResourceDataModelBase::repeatText(const KAEvent& event) 0504 { 0505 const QString repText = event.recurrenceText(true); 0506 return repText.isEmpty() ? event.repetitionText(true) : repText; 0507 } 0508 0509 /****************************************************************************** 0510 * Return a string for sorting the repetition column. 0511 */ 0512 QString ResourceDataModelBase::repeatOrder(const KAEvent& event) 0513 { 0514 int repOrder = 0; 0515 int repInterval = 0; 0516 if (event.repeatAtLogin()) 0517 repOrder = 1; 0518 else 0519 { 0520 repInterval = event.recurInterval(); 0521 switch (event.recurType()) 0522 { 0523 case KARecurrence::MINUTELY: 0524 repOrder = 2; 0525 break; 0526 case KARecurrence::DAILY: 0527 repOrder = 3; 0528 break; 0529 case KARecurrence::WEEKLY: 0530 repOrder = 4; 0531 break; 0532 case KARecurrence::MONTHLY_DAY: 0533 case KARecurrence::MONTHLY_POS: 0534 repOrder = 5; 0535 break; 0536 case KARecurrence::ANNUAL_DATE: 0537 case KARecurrence::ANNUAL_POS: 0538 repOrder = 6; 0539 break; 0540 case KARecurrence::NO_RECUR: 0541 default: 0542 break; 0543 } 0544 } 0545 return QStringLiteral("%1%2").arg(static_cast<char>('0' + repOrder)).arg(repInterval, 8, 10, QLatin1Char('0')); 0546 } 0547 0548 /****************************************************************************** 0549 * Returns the QWhatsThis text for a specified column. 0550 */ 0551 QString ResourceDataModelBase::whatsThisText(int column) 0552 { 0553 switch (column) 0554 { 0555 case TimeColumn: 0556 return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm"); 0557 case TimeToColumn: 0558 return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm"); 0559 case RepeatColumn: 0560 return i18nc("@info:whatsthis", "How often the alarm recurs"); 0561 case ColourColumn: 0562 return i18nc("@info:whatsthis", "Background color of alarm message"); 0563 case TypeColumn: 0564 return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)"); 0565 case NameColumn: 0566 return i18nc("@info:whatsthis", "Alarm name, or alarm text if name is blank"); 0567 case TextColumn: 0568 return i18nc("@info:whatsthis", "Alarm message text, URL of text file to display, command to execute, or email subject line"); 0569 case TemplateNameColumn: 0570 return i18nc("@info:whatsthis", "Name of the alarm template"); 0571 default: 0572 return {}; 0573 } 0574 } 0575 0576 /****************************************************************************** 0577 * Return the icon associated with an event's action. 0578 */ 0579 QPixmap* ResourceDataModelBase::eventIcon(const KAEvent& event) 0580 { 0581 switch (event.actionTypes()) 0582 { 0583 case KAEvent::Action::Email: 0584 return mEmailIcon; 0585 case KAEvent::Action::Audio: 0586 return mAudioIcon; 0587 case KAEvent::Action::Command: 0588 return mCommandIcon; 0589 case KAEvent::Action::Display: 0590 if (event.actionSubType() == KAEvent::SubAction::File) 0591 return mFileIcon; 0592 [[fallthrough]]; // fall through to DisplayCommand 0593 case KAEvent::Action::DisplayCommand: 0594 default: 0595 return mTextIcon; 0596 } 0597 } 0598 0599 /****************************************************************************** 0600 * Display a message to the user. 0601 */ 0602 void ResourceDataModelBase::handleResourceMessage(ResourceType::MessageType type, const QString& message, const QString& details) 0603 { 0604 if (type == ResourceType::MessageType::Error) 0605 { 0606 qCDebug(KALARM_LOG) << "Resource Error!" << message << details; 0607 KAMessageBox::detailedError(Desktop::mainWindow(), message, details); 0608 } 0609 else if (type == ResourceType::MessageType::Info) 0610 { 0611 qCDebug(KALARM_LOG) << "Resource user message:" << message << details; 0612 // KMessageBox::informationList looks bad, so use our own formatting. 0613 const QString msg = details.isEmpty() ? message : message + QStringLiteral("\n\n") + details; 0614 KAMessageBox::information(Desktop::mainWindow(), msg); 0615 } 0616 } 0617 0618 bool ResourceDataModelBase::isMigrationComplete() const 0619 { 0620 return mMigrationStatus == 1; 0621 } 0622 0623 bool ResourceDataModelBase::isMigrating() const 0624 { 0625 return mMigrationStatus == 0; 0626 } 0627 0628 void ResourceDataModelBase::setMigrationInitiated(bool started) 0629 { 0630 mMigrationStatus = (started ? 0 : -1); 0631 } 0632 0633 void ResourceDataModelBase::setMigrationComplete() 0634 { 0635 mMigrationStatus = 1; 0636 if (mCreationStatus) 0637 Resources::notifyResourcesCreated(); 0638 } 0639 0640 void ResourceDataModelBase::setCalendarsCreated() 0641 { 0642 mCreationStatus = true; 0643 if (mMigrationStatus == 1) 0644 Resources::notifyResourcesCreated(); 0645 } 0646 0647 /****************************************************************************** 0648 * Return the alarm time text in the form "date time". 0649 * Parameters: 0650 * dateTime = the date/time to format. 0651 * leadingZero = the character to represent a leading zero, or '\0' for no leading zeroes. 0652 */ 0653 QString ResourceDataModelBase::alarmTimeText(const DateTime& dateTime, char leadingZero) 0654 { 0655 // Whether the date and time contain leading zeroes. 0656 static bool leadingZeroesChecked = false; 0657 static QString timeFormat; // time format for current locale 0658 static QString timeFullFormat; // time format with leading zero, if different from 'timeFormat' 0659 static int hourOffset = 0; // offset within time string to the hour 0660 0661 if (!dateTime.isValid()) 0662 return i18nc("@info Alarm never occurs", "Never"); 0663 QLocale locale; 0664 if (!leadingZeroesChecked) 0665 { 0666 // Check whether the day number and/or hour have no leading zeroes, if 0667 // they are at the start of the date/time. If no leading zeroes, they 0668 // will need to be padded when displayed, so that displayed dates/times 0669 // can be aligned with each other. 0670 // Note that if leading zeroes are not included in other components, no 0671 // alignment will be attempted. 0672 0673 // Check the time format. 0674 // Remove all but hours, minutes and AM/PM, since alarms are on minute 0675 // boundaries. Preceding separators are also removed. 0676 timeFormat = locale.timeFormat(QLocale::ShortFormat); 0677 for (int del = 0, predel = 0, c = 0; c < timeFormat.size(); ++c) 0678 { 0679 char ch = timeFormat.at(c).toLatin1(); 0680 switch (ch) 0681 { 0682 case 'H': 0683 case 'h': 0684 case 'm': 0685 case 'a': 0686 case 'A': 0687 if (predel == 1) 0688 { 0689 timeFormat.remove(del, c - del); 0690 c = del; 0691 } 0692 del = c + 1; // start deleting from the next character 0693 if ((ch == 'A' && del < timeFormat.size() && timeFormat.at(del).toLatin1() == 'P') 0694 || (ch == 'a' && del < timeFormat.size() && timeFormat.at(del).toLatin1() == 'p')) 0695 ++c, ++del; 0696 predel = -1; 0697 break; 0698 0699 case 's': 0700 case 'z': 0701 case 't': 0702 timeFormat.remove(del, c + 1 - del); 0703 c = del - 1; 0704 if (!predel) 0705 predel = 1; 0706 break; 0707 0708 default: 0709 break; 0710 } 0711 } 0712 0713 // 'HH' and 'hh' provide leading zeroes; single 'H' or 'h' provide no 0714 // leading zeroes. 0715 static const QRegularExpression hourReg(QStringLiteral("[hH]")); 0716 int i = timeFormat.indexOf(hourReg); 0717 static const QRegularExpression hourMinAmPmReg(QStringLiteral("[hHmaA]")); 0718 int first = timeFormat.indexOf(hourMinAmPmReg); 0719 if (i >= 0 && i == first && (i == timeFormat.size() - 1 || timeFormat.at(i) != timeFormat.at(i + 1))) 0720 { 0721 timeFullFormat = timeFormat; 0722 timeFullFormat.insert(i, timeFormat.at(i)); 0723 // Find index to hour in formatted times 0724 const QTime t(1,30,30); 0725 const QString nozero = locale.toString(t, timeFormat); 0726 const QString zero = locale.toString(t, timeFullFormat); 0727 for (int j = 0; j < nozero.size(); ++j) 0728 if (nozero[j] != zero[j]) 0729 { 0730 hourOffset = j; 0731 break; 0732 } 0733 } 0734 0735 leadingZeroesChecked = true; 0736 } 0737 0738 const KADateTime kdt = dateTime.effectiveKDateTime().toTimeSpec(Preferences::timeSpec()); 0739 QString dateTimeText = locale.toString(kdt.date(), QLocale::ShortFormat); 0740 0741 if (!dateTime.isDateOnly() || kdt.utcOffset() != dateTime.utcOffset()) 0742 { 0743 // Display the time of day if it's a date/time value, or if it's 0744 // a date-only value but it's in a different time zone 0745 dateTimeText += QLatin1Char(' '); 0746 // Don't try to align right-to-left languages... 0747 const bool useFullFormat = QApplication::isLeftToRight() && leadingZero && !timeFullFormat.isEmpty(); 0748 QString timeText = locale.toString(kdt.time(), (useFullFormat ? timeFullFormat : timeFormat)); 0749 if (useFullFormat && leadingZero != '0' && timeText.at(hourOffset) == QLatin1Char('0')) 0750 timeText[hourOffset] = QChar::fromLatin1(leadingZero); 0751 dateTimeText += timeText; 0752 } 0753 return dateTimeText + QLatin1Char(' '); 0754 } 0755 0756 /****************************************************************************** 0757 * Return the time-to-alarm text. 0758 */ 0759 QString ResourceDataModelBase::timeToAlarmText(const DateTime& dateTime) 0760 { 0761 if (!dateTime.isValid()) 0762 return i18nc("@info Alarm never occurs", "Never"); 0763 const KADateTime now = KADateTime::currentUtcDateTime(); 0764 if (dateTime.isDateOnly()) 0765 { 0766 const int days = now.date().daysTo(dateTime.date()); 0767 // xgettext: no-c-format 0768 return i18nc("@info n days", "%1d", days); 0769 } 0770 const int mins = (now.secsTo(dateTime.effectiveKDateTime()) + 59) / 60; 0771 if (mins <= 0) 0772 return {}; 0773 QLocale locale; 0774 QString minutes = locale.toString(mins % 60); 0775 if (minutes.size() == 1) 0776 minutes.prepend(locale.zeroDigit()); 0777 if (mins < 24*60) 0778 return i18nc("@info hours:minutes", "%1:%2", mins/60, minutes); 0779 // If we render a day count, then we zero-pad the hours, to make the days line up and be more scanable. 0780 const int hrs = mins / 60; 0781 QString hours = locale.toString(hrs % 24); 0782 if (hours.size() == 1) 0783 hours.prepend(locale.zeroDigit()); 0784 const QString days = locale.toString(hrs / 24); 0785 return i18nc("@info days hours:minutes", "%1d %2:%3", days, hours, minutes); 0786 } 0787 0788 // vim: et sw=4: